Sommaire

BONJOUR
Musique "Invention BWV784" J.S. BACH 1685-1750

Tutoriel et Help

OpenGL Library B

Nous veillons aux respects des droits d'auteur : si par erreur un contenu protégé est disponible sans indication sur ce site, merci de contacter l'administrateur, il sera retiré ou mis à jour dans les plus brefs délais.


Vu dans ... https://fr.wikipedia.org/wiki/OpenGL

OpenGL

Un article de Wikipédia, l'encyclopédie libre.
Un traitement de pipeline 3D (Graphics Pipeline Process).

OpenGL (Open Graphics Library) est un ensemble normalisé de fonctions de calcul d'images 2D ou 3D lancé par Silicon Graphics en 1992[1. Cette interface de programmation est disponible sur de nombreuses plateformes où elle est utilisée pour des applications qui vont du jeu vidéo jusqu'à la CAO en passant par la modélisation[1.

OpenGL permet à un programme de déclarer la géométrie d'objets sous forme de points, de vecteurs, de polygones, de bitmaps et de textures. OpenGL effectue ensuite des calculs de projection en vue de déterminer l'image à l'écran, en tenant compte de la distance, de l'orientation, des ombres, de la transparence et du cadrage[2,[3.

L'interface regroupe environ 250 fonctions différentes qui peuvent être utilisées pour afficher des scènes tridimensionnelles complexes à partir de simples primitives géométriques. Du fait de son ouverture, de sa souplesse d'utilisation et de sa disponibilité sur toutes les plates-formes, elle est utilisée par la majorité des applications scientifiques, industrielles ou artistiques 3D et certaines applications 2D vectorielles. Cette bibliothèque est également utilisée dans l'industrie du jeu vidéo où elle est souvent en rivalité avec la bibliothèque de Microsoft : Direct3D. Une version nommée OpenGL ES a été conçue spécifiquement pour les applications embarquées (téléphones portables, agenda de poche, consoles de jeux…).

Sommaire

[masquer]
  • 1 Histoire
    • 1.1 OpenGL 1.5
    • 1.2 OpenGL 2.0
    • 1.3 OpenGL ES
    • 1.4 OpenGL 3.0
    • 1.5 OpenGL 4.0
    • 1.6 Vulkan
  • 2 Extensions
  • 3 Bibliothèques
  • 4 Exemple simple de programme OpenGL
  • 5 Implémentations
  • 6 Intérêt d'OpenGL pour Silicon Graphics
  • 7 Arguments pour et contre OpenGL
  • 8 Utilisations
  • 9 Critiques
  • 10 Voir aussi
    • 10.1 Bibliographie
    • 10.2 Spécifications
    • 10.3 Notes et références
    • 10.4 Articles connexes
    • 10.5 Liens externes

Histoire

OpenGL est une évolution d'IrisGL, API 3D développée par Silicon Graphics (SGI). Cette dernière étant difficile à faire évoluer et à étendre, il a été décidé chez Silicon Graphics qu'OpenGL pouvait être assimilé à un sur-ensemble d'IrisGL. Les spécifications et développements de base ont été réalisés par une équipe de Silicon Graphics. Le projet Fahrenheit, initiative de Microsoft et de Silicon Graphics, tenta d'unifier les interfaces OpenGL et Direct3D. Celui-ci apporta au début l'espoir de mettre de l'ordre dans le monde des API 3D, mais pour des contraintes financières de la part de Silicon Graphics, le projet dut être abandonné.

La spécification OpenGL est actuellement surveillée par l'Architecture Review Board (ARB), formé en 1992. L'ARB se compose d'entreprises ayant un profond intérêt pour la création d'une API cohérente et largement disponible. Selon le site officiel d'OpenGL, AMD/ATI, Apple, Dell, Evans & Sutherland, Hewlett-Packard, IBM, Intel, Matrox, Nvidia, SGI et Sun font partie des membres votants (juin 2002). Microsoft, l'un des membres fondateurs, s'est retiré en mars 2003.

OpenGL 1.5

Le 29 juillet 2003 est annoncé OpenGL 1.5. Au même moment, ARB annonce les spécifications d'une première ébauche de l'OpenGL Shading Language ainsi que les trois extensions ARB_shader_objects, ARB_vertex_shader et ARB_fragment_shader.

OpenGL 2.0

Le 7 septembre 2004 est annoncé OpengGL 2.0. l'OpenGL Shading Language (GLSL, également surnommé GLslang) à la syntaxe proche du langage C y est intégré dans une version définitive.

OpenGL ES

Le 31 juillet 2006 lors de la conférence de la Siggraph, l'ARB a annoncé sa décision de transférer le contrôle de la spécification OpenGL au Khronos Group, qui s'occupait déjà de différentes spécifications OpenGL pour les systèmes embarqués et les consoles de jeux vidéo dont OpenGL ES. L'Architecture Review Board a été dissoute le 21 septembre 2006, mais pour des raisons historiques, l'acronyme « ARB » a été conservé[4]<>.

OpenGL 3.0

Les spécifications officielles de OpenGL 3.0 ont été présentées le 11 août 2008. À l'origine, OpenGL 3 devait constituer un changement majeur, avec une refonte totale de l'API pour la rendre plus concurrentielle face à Direct3D. Il était notamment question d'abandonner les fonctionnalités obsolètes qui n'étaient, jusque là, conservées que par souci de compatibilité avec les anciennes versions, l'utilisation d'un nouveau modèle objet plus cohérent qui encapsule notamment les états, etc[5. Cependant, après un an de retard et une absence totale de communication de la part du groupe Khronos, cette refonte totale a été abandonnée au profit d'une mise à jour incrémentale permettant simplement de supporter les dernières technologies graphiques[6. Cette décision serait due à la volonté de rassurer certains marchés, par exemple en CAO, qui ne souhaitaient pas voir disparaître ces anciennes fonctionnalités[7. Malgré tout, les fonctions obsolètes seront marquées « deprecated » dans les nouvelles spécifications, et pourraient être supprimées dans les versions futures.

OpenGL 4.0

Le 11 mars 2010, OpenGL 4.0 est annoncé, il a pour but d'exploiter pleinement le matériel compatible avec DirectX 11.

Au même moment OpenGL 3.3 est également annoncé.

Vulkan

Au début annoncé sous l’appellation OpenGL Next, Vulkan revoit d'importants concept d'OpenGL afin de s'adapter aux 25 ans d'évolution des processeurs graphiques 3D depuis la première version. La compatibilité avec les anciennes versions n'est plus assurée.

Les principaux changements les plus notables sont :

  • OpenGL et OpenGL ES sont unifiés ;
  • Les pilotes sont simplifiés, le processeur contrôle lui même directement le GPU et la mémoire utilisée par celui-ci.

Extensions

La norme OpenGL permet à différents fabricants d'ajouter de nouvelles fonctionnalités sous forme d'extensions. Une extension est distribuée en 2 parties : un fichier d'en-têtes qui contient les fonctions prototypes de l'extension et les drivers du fabricant. Chacun d'eux possède une abréviation qui est utilisée pour nommer leurs nouvelles fonctions et constantes. Par exemple, l'abréviation de nVidia (« NV ») est utilisée pour définir leur fonction propriétaire « glCombinerParameterfvNV() » et leur constante « GL_NORMAL_MAP_NV ». Il peut arriver que plus d'un fabricant implémente la même fonctionnalité. Dans ce cas, l'abréviation « EXT » est utilisée. Il peut également arriver que l'ARB officialise une extension. Celle-ci devient alors un standard et l'abréviation « ARB » est utilisée. La première extension ARB fut GL_ARB_multitexture.

Bibliothèques

Plusieurs bibliothèques sont développées à partir d'OpenGL afin d'apporter des fonctionnalités qui ne sont pas disponibles dans la bibliothèque OpenGL elle-même :

  • GLU ;
  • GLUT ;
  • GLUI ;
  • GLEW ;
  • GLEE ;
  • GLFW ;
  • GLM.

On notera en particulier la bibliothèque OpenGL Performer, développée par Silicon Graphics et disponible pour IRIX, Linux et quelques versions de Windows, qui permet la création d'applications de rendus temps réel.

Exemple simple de programme OpenGL

Voici un programme simple, en 2D, dessinant un triangle et permettant de modifier instantanément sa couleur en pressant l'une des touches r, g, b et celle du fond avec R, G, B. La touche q quitte le programme[8. Celui-ci, qui utilise GLUT pour simplifier la programmation, est compilé et exécuté, sous Linux, parcc colorswap.c -lglut -lGL -lGLU && ./a.out. Les bibliothèques OpenGL sont juste à installer depuis les dépôts de votre distribution particulière, ce qui est très simple. L'objectif du code est ici la lisibilité et non la performance.

Sortie initiale du programme OpenGL "colorswap.c". Il s'agit d'une simple démonstration montrant les premiers concepts. A ce stade, OpenGL n'est utilisé que comme le GDDM (graphical data display manager) d'IBM : en 2D.
Une pression sur la touche g (pour green) change instantanément la couleur du triangle, montrant la vélocité d'OpenGL et son intérêt même en 2D pour des graphiques interactifs simples.
La pression de R (pour red, majuscule ici pour indiquer que cela concerne le fond) bascule tout aussi instantanément le fond en rouge.
/*
 * colorswap.c - Options -lglut -lGL -lGLU :
 * cc colorswap.c -lglut -lGL -lGLU && ./a.out
 *
 * Donnez le focus à la fenêtre, puis pressez :
 * R G B ou Y pour couleur du fond
 * r g b ou y pour celle de l'encre
 * q pour quitter.
 *
 * Programme écrit sous OpenSuSE avec Geany sous
 * Sony Satellite G50 bootant Linux d'un SSD USB3
 * en inhibant le secure boot et en donnant la
 * priorité de boot à l'USB.
 */

#include <GL/glut.h>

int firstPass = 1; // simule le « once » d'Eiffel

// Fonction associée (« callback ») à la fenêtre.
// Elle redessine tout quand on redimensionne.
void Rafraichir(void)
{
  glClear(GL_COLOR_BUFFER_BIT);	// Effacer la surface graphique

  if (firstPass) {
    glColor3f(0, 0, 1.0);  // Encre bleue au départ
    firstPass = 0;
  }

  // Dessiner un triangle
  glBegin(GL_POLYGON); 	// Commencer un polygone
  glVertex2i(100, 100); // Coordonnées des trois points
  glVertex2i(400, 100);
  glVertex2i(250, 400);
  glEnd(); 				// Fermer le polygone
  glFlush(); 			// Dessiner le polygone
}

// Action sur frappe d'une touche au clavier
void Clavier(unsigned char key, int x, int y)
{
  switch(key) {
    case 'q' : exit(0); // q => « quitter »

    case 'R' : glClearColor(1, 0, 0, 0); break; 	// R G B : couleur fond
    case 'G' : glClearColor(0, 1, 0, 0); break;
    case 'B' : glClearColor(0, 0, 1, 0); break;
    case 'Y' : glClearColor(1, 1, 0, 0); break; 	// Y : jaune

    case 'r' : glColor3f(1, 0, 0); break; 	// r g b : couleur encre
    case 'g' : glColor3f(0, 1, 0); break;
    case 'b' : glColor3f(0, 0, 1); break;
    case 'y' : glColor3f(1, 1, 0); break; 	// y ; jaune
  }

  Rafraichir();	// Affichage
}

int main(int argc, char *argv[])
{
  int win; 					// Identificateur de fenêtre (une seule)
  glutInit(&argc, argv); 			// initialise GLUT
  glutInitDisplayMode(GLUT_RGB); 	// On travaille en RGB
  glutInitWindowSize(500,400);   	// 500px large, 400px haut
  win = glutCreateWindow("Pressez r/g/b/y/R/G/B/Y/q");

  // Ordres maintenant exécutés dans la fenêtre « win »
  glClearColor(0.9, 0.9, 0.2, 0); 	// Jaune
  gluOrtho2D(0,600,0,600);			// On garde ces coordonnées

  glutDisplayFunc(Rafraichir); 		// Callback de la fenêtre
  glutKeyboardFunc(Clavier); 		// Callback du clavier

  glutMainLoop();					// Boucle infinie

  /* Le programme n'atteint jamais ce point */

  return 0;
}

Implémentations

Plusieurs implémentations d'OpenGL (exploitant l'accélération 3D fournie par le matériel) existent pour Windows, de nombreuses stations Unix et Mac OS. Ces implémentations sont généralement fournies par les constructeurs de matériels graphiques et y sont étroitement liées. Il existe une implémentation libre de cette bibliothèque, nommée Mesa, créée en 1993 par Brian Paul et utilisant la même API, ce qui permettait :

  • de se passer de la licence OpenGL dans la plupart des cas[9, l'implémentation de SGI étant sous licence, mais l'API elle-même ne pouvant l'être ;
  • de faire tourner des applications OpenGL sur des terminaux X de base qui y étaient inaptes (les performances sont alors quelconques faute d'accélération matérielle, mais cela vaut souvent mieux que pas d'exécution du tout et reste acceptable si l'on n'a pas besoin d'animation en temps réel). Ces stations ne disposant pas en général d'accélération 3D matérielle, on ne peut les utiliser que pour des modèles simples comportant relativement peu de polygones : de quelques dizaines à quelques milliers.

Intérêt d'OpenGL pour Silicon Graphics

Ce modèle est-il pertinent ? Cliquez pour en voir d'autres.
[afficher Cette section ne cite pas suffisamment ses sources (indiquez la date de pose grâce au paramètre date).
Pour l'améliorer, ajoutez des références vérifiables [Comment faire ?] ou le modèle {{Référence nécessaire}} sur les passages nécessitant une source.

Silicon Graphics a eu pour politique de mettre à chaque fois dans le domaine public l'avant-dernière version de GL, bibliothèque graphique de GL. Cette approche :

  • évitait la concurrence (OpenGL étant gratuit et ouvert, pourquoi le réinventer ?) ;
  • dissuadait de modifier OpenGL, car tout ajout serait à recopier dès la version suivante ;
  • donnait aux stations Silicon Graphics un avantage notable, GL ayant toujours une version d'avance sur OpenGL ;
  • constituait une forme de mécénat bon pour le "karma" de la société dans les milieux Open Source

Arguments pour et contre OpenGL

Dans la pratique, tout en continuant à assurer un certain support d'OpenGL, les constructeurs et éditeurs de logiciel ont mis d'autres cordes à leur arc, comme Direct3D pour Microsoft (Google Earth permet par exemple un choix entre OpenGL et Direct3D). L'une des raisons était de mieux tirer parti d'innovation matérielles introduites dans les cartes graphiques, comme le nombre de processeurs, leur spécialisation ou non, etc.

En sens inverse a été cependant développé WebGL, qui donne des potentialités 3D à n'importe quel navigateur qui le supporte. OpenGL garde donc l'atout de la généralité, tandis que des solutions plus spécialisées restent préférées pour les applications demandant des calculs graphiques intensifs, comme les jeux. Voir aussi SDL.

Utilisations

Certains logiciels utilisent OpenGL pour gérer l'ensemble de leur interface, même 2D, comme Blender, Google Earth (version OpenGL), GLtron, XBMC, ou la version SGI de X11.

Critiques

Rich Geldreich et Joshua Barczak, sur leur blogs personnels, critiquent OpenGL[10,[11. Leurs doléances suggérant d'un coté le besoin d'une remise à plat totale à la lumière de l'expérience acquise, de l'autre le besoin de mieux maîtriser la dispersion des portages. Il s'agit cependant d'utilisateurs à haute technicité devant utiliser les fonctions les plus avancées des cartes graphiques de 2014.


La géométrie en OpenGL : vers les VertexBufferObject

Vu dans ... http://raptor.developpez.com/tutorial/opengl/vbo/

Pourquoi faire un tutoriel sur la géométrie en OpenGL ? Pourquoi faut-il s'y intéresser ? La réponse est simple, les technologies 3D sont sans cesse en cours d'évolution et d'améliorations. OpenGL ne fait pas exception, il y a régulièrement de nouvelles extensions pour améliorer ses performances et son efficacité. L'une d'elle se concentre sur l'amélioration de la géométrie : les Vertex Buffer Object. Comment en est on arrivé là ?

Publié le 5 août 2008 - Mis à jour le 15 août 2008

Introduction

Vous trouvez que votre application est lente ? Peut-être avez vous choisi un mauvais système pour rendre tous vos modèles 3D dans votre application ou votre jeu. Ce tutoriel a pour objectif de vous présenter toutes les techniques pour rendre des objets en OpenGL avec leurs avantages et leurs inconvénients. Nous commencerons par une présentation de la première méthode utilisée par OpenGL pour rendre des objets, le mode immédiat. Nous verrons en quoi ce système n'est pas forcément adapté au rendu et pourquoi les "VertexArray" ont fait leur apparition. Enfin, nous verrons en détail l'utilisation de la dernière, la plus récente et nouvelle technologie que sont les "Vertex Buffer Object".

Au travers de ce tutoriel, nous verrons le fonctionnement général de chacune des technologies au travers de l'exemple simple qu'est le dessin d'un cube coloré. Nous ferons une petite étude du coût de chaque méthode pour pouvoir les comparer et savoir dans quels cas utiliser l'une plutôt que l'autre.

Rendu de notre cube

II. Les prémices d'OpenGL : la géométrie immédiate

II-A. Rappel sur le dessin immédiat

La première méthode utilise, pour dessiner des formes 3D dans notre application, les fonctions "immédiates". Ce type de dessin est le premier que l'on apprend lorsque que l'on découvre l'API OpenGL. Cette méthode est dite "immédiate" car elle consiste en une succession d'appels de fonctions qui dessineront directement à l'écran. Pour décrire la géométrie, on utilise un ensemble de fonctions qui décrira toutes les composantes de notre objet 3D dessiné.

Chaque description de forme est encadrée par les fonctions glBegin() et glEnd() qui permettent de délimiter notre dessin. La première prend comme paramètre le type de forme à dessiner. Voici une description de ces fonctions :

Prototype de la fonction | Description
void glBegin(GLenum mode); | Marque le début du dessin d'un groupe de primitives. mode précise le type des primitives parmi GL_POINTS, GL_LINES, GL_LINE_STRIP, GL_LINE_LOOP, GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_QUADS, GL_QUAD_STRIP et GL_POLYGON.
void glEnd(void); | Marque la fin du dessin d'un groupe de primitives.

Chaque primitive est découpée en vertex qui correspondent à un sommet de notre forme 3D. Nous pouvons définir plusieurs types d'informations par vertex comme sa position, sa couleur, ... Chaque information est définie par un appel de fonction correspondant. Par exemple, nous utiliserons glVertex*() pour définir la position du vertex et glColor*() pour en définir la couleur. Ces fonctions sont décrites ici :

Prototype de la fonction | Description
void glVertex*(TYPE paramètres); | Spécifie la position du vertex à dessiner
void glNormal*(TYPE paramètres); | Spécifie la normale du vertex
void glColor*(TYPE paramètres); | Spécifie la couleur du vertex
void glSecondaryColor*(TYPE paramètres); | Spécifie la seconde couleur du vertex
void glIndex*(TYPE paramètres); | Spécifie l'index de la couleur du vertex
void glTexCoord*(TYPE paramètres); | Spécifie les coordonnées de la texture principale du vertex
void glMultiTexCoord*(TYPE paramètres); | Spécifie les coordonnées de texture en cas de "multi-texturing" du vertex
void glFogCoord*(TYPE paramètres); | Spécifie les coordonnées de fog du vertex

* doit être remplacé par la description du TYPE de données passé en paramètre.

II-B. Utilisation dans notre exemple

Pour l'exemple du cube, que nous allons suivre tout au long du tutoriel, nous allons créer un cube dont chacun des vertex a une couleur différente. Nous allons constituer notre cube de 6 faces. Chacune des faces est constituée de 2 triangles dont chaque vertex à une couleur différente. Nous obtenons donc un cube de 12 triangles.

Dans le cas que nous étudions actuellement, nous allons choisir le mode de primitives GL_TRIANGLES pour rendre notre objet. Ainsi, pour un cube, nous devons appeler 36 fois la fonction glVertex*() ( 12 triangles * 3 vertex ). Il faut multiplier ce nombre par la quantité d'information par vertex pour obtenir le nombre total d'appels de fonction. Dans un cas extrême qui paramètrerait une couleur par vertex, la normale au vertex, des coordonnées de fog et les coordonnées pour deux textures, nous obtiendrions 180 appels de fonctions pour un simple cube avec 12 triangles. Pour notre exemple, nous nous contenterons d'une seule propriété de couleur par vertex donc nous obtiendrons 72 appels de fonctions plus les 2 appels pour glBegin et glEnd. Enfin, trois valeurs par appel de fonction nous donnent un total de 216 valeurs envoyées au processeur graphique.

Rendu d'un cube en mode immédiat
Sélectionnez

glBegin( GL_TRIANGLES );

    // gauche (x=-1)
    glColor3f( 1.0f, 0.0f, 0.0f ); glVertex3f( -1.0f, 1.0f, -1.0f );
    glColor3f( 1.0f, 0.0f, 1.0f ); glVertex3f( -1.0f, -1.0f, -1.0f );
    glColor3f( 1.0f, 1.0f, 1.0f ); glVertex3f( -1.0f, 1.0f, 1.0f );
    glColor3f( 1.0f, 1.0f, 1.0f ); glVertex3f( -1.0f, 1.0f, 1.0f );
    glColor3f( 1.0f, 0.0f, 1.0f ); glVertex3f( -1.0f, -1.0f, -1.0f );
    glColor3f( 0.0f, 0.0f, 1.0f ); glVertex3f( -1.0f, -1.0f, 1.0f );

    // droite (x=1)
    glColor3f( 0.0f, 1.0f, 0.0f ); glVertex3f( 1.0f, 1.0f, 1.0f );
    glColor3f( 0.0f, 1.0f, 1.0f ); glVertex3f( 1.0f, -1.0f, 1.0f );
    glColor3f( 1.0f, 1.0f, 0.0f ); glVertex3f( 1.0f, 1.0f, -1.0f );
    glColor3f( 1.0f, 1.0f, 0.0f ); glVertex3f( 1.0f, 1.0f, -1.0f );
    glColor3f( 0.0f, 1.0f, 1.0f ); glVertex3f( 1.0f, -1.0f, 1.0f );
    glColor3f( 1.0f, 1.0f, 1.0f ); glVertex3f( 1.0f, -1.0f, -1.0f );

    // bas (y=-1)
    glColor3f( 0.0f, 0.0f, 1.0f ); glVertex3f( -1.0f, -1.0f, 1.0f );
    glColor3f( 1.0f, 0.0f, 1.0f ); glVertex3f( -1.0f, -1.0f, -1.0f );
    glColor3f( 0.0f, 1.0f, 1.0f ); glVertex3f( 1.0f, -1.0f, 1.0f );
    glColor3f( 0.0f, 1.0f, 1.0f ); glVertex3f( 1.0f, -1.0f, 1.0f );
    glColor3f( 1.0f, 0.0f, 1.0f ); glVertex3f( -1.0f, -1.0f, -1.0f );
    glColor3f( 1.0f, 1.0f, 1.0f ); glVertex3f( 1.0f, -1.0f, -1.0f );

    // haut (y=1)
    glColor3f( 1.0f, 0.0f, 0.0f ); glVertex3f( -1.0f, 1.0f, -1.0f );
    glColor3f( 1.0f, 1.0f, 1.0f ); glVertex3f( -1.0f, 1.0f, 1.0f );
    glColor3f( 1.0f, 1.0f, 0.0f ); glVertex3f( 1.0f, 1.0f, -1.0f );
    glColor3f( 1.0f, 1.0f, 0.0f ); glVertex3f( 1.0f, 1.0f, -1.0f );
    glColor3f( 1.0f, 1.0f, 1.0f ); glVertex3f( -1.0f, 1.0f, 1.0f );
    glColor3f( 0.0f, 1.0f, 0.0f ); glVertex3f( 1.0f, 1.0f, 1.0f );

    // arriere (z=-1)
    glColor3f( 1.0f, 1.0f, 0.0f ); glVertex3f( 1.0f, 1.0f, -1.0f );
    glColor3f( 1.0f, 1.0f, 1.0f ); glVertex3f( 1.0f, -1.0f, -1.0f );
    glColor3f( 1.0f, 0.0f, 0.0f ); glVertex3f( -1.0f, 1.0f, -1.0f );
    glColor3f( 1.0f, 0.0f, 0.0f ); glVertex3f( -1.0f, 1.0f, -1.0f );
    glColor3f( 1.0f, 1.0f, 1.0f ); glVertex3f( 1.0f, -1.0f, -1.0f );
    glColor3f( 1.0f, 0.0f, 1.0f ); glVertex3f( -1.0f, -1.0f, -1.0f );

    // avant (z=1)
    glColor3f( 1.0f, 1.0f, 1.0f ); glVertex3f( -1.0f, 1.0f, 1.0f );
    glColor3f( 0.0f, 0.0f, 1.0f ); glVertex3f( -1.0f, -1.0f, 1.0f );
    glColor3f( 0.0f, 1.0f, 0.0f ); glVertex3f( 1.0f, 1.0f, 1.0f );
    glColor3f( 0.0f, 1.0f, 0.0f ); glVertex3f( 1.0f, 1.0f, 1.0f );
    glColor3f( 0.0f, 0.0f, 1.0f ); glVertex3f( -1.0f, -1.0f, 1.0f );
    glColor3f( 0.0f, 1.0f, 1.0f ); glVertex3f( 1.0f, -1.0f, 1.0f );

glEnd();

II-C. Avantages / Inconvénients de cette technique

Notre premier exemple permet de mettre en évidence certains avantages et certains inconvénients de la technique de dessin "Immédiat". Nous allons donc commencer par évoquer les avantages d'une telle technique :

  • Permet de définir indépendamment chacune des informations des vertex
  • Permet d'augmenter la lisibilité du code
  • Regroupe clairement chaque rendu par type de primitives

Malgré ceux-ci, on remarque rapidement un grand nombre d'inconvénients très gênants pour des applications "temps-réel". En voici une liste non-exhaustive :

  • Un grand nombre d'appels de fonction pour des géométries simples
  • Grande répétitivité de commandes identiques
  • Complexifie les structures de données utilisables
  • Obligation de séparer chacune des composantes de nos informations (X, Y et Z pour les positions de nos vertex, par exemple)

La technique de dessin "Immédiat" est très contraignante du point de vue performance pour une application "temps-réel". Les deux problèmes principaux sont que le transfert des données est extrêmement lent au vu de la quantité d'appel de fonctions et du peu d'informations envoyées au GPU par chaque appel. Le second problème vient du nombre d'appels identiques à chaque frame. OpenGL nous propose une nouvelle technique permettant de résoudre notamment le nombre d'appels de fonctions, de limiter l'envoi d'information en double. Cette technique utilise le principe des "vertex array". De plus, cette technique pourra être complétée par la méthode des "Display List" qui évitera un grand nombre d'appels de fonction répétitifs à chaque frame dans le cas de géométrie "statique".

III. Quelques avancées : les "vertex array" (VA)

III-A. Utilisation des Vertex Array

La seconde technique utilisable en OpenGL est donc celle des "Vertex Array". Le principe de cette technique est de rassembler l'ensemble des vertex d'une géométrie dans un tableau unique. Il peut y avoir un tableau par type d'information ou un seul tableau "entrelacé" contenant toutes les informations. L'utilisation des Vertex Array se fait en trois étapes principales. La première consiste en l'activation / désactivation des types de tableaux à utiliser. Ensuite, il faut remplir les tableaux activés avec les informations de notre géométrie. Enfin, on peut lancer l'appel du rendu de notre géométrie.

La première chose à faire pour utiliser les VertexArray est d'en activer l'utilisation. Il faut également les désactiver quand on n'en a plus besoin. Pour cela, on utilise deux fonctions :

Prototype de la fonction | Description
void glEnableClientState(GLenum type); | Spécifie le type de tableau à activer
void glDisableClientState(GLenum type); | Spécifie le type de tableau à désactiver

Le type de tableau est à choisir parmi GL_COLOR_ARRAY, GL_EDGE_FLAG_ARRAY, GL_FOG_COORD_ARRAY, GL_INDEX_ARRAY, GL_NORMAL_ARRAY, GL_SECONDARY_COLOR_ARRAY, GL_TEXTURE_COORD_ARRAY et GL_VERTEX_ARRAY.

La seconde étape consiste à indiquer à OpenGL où se trouvent les données à utiliser pour dessiner notre géométrie. On va donc créer un tableau local qui sera directement utilisé par OpenGL. Il existe ainsi une fonction par type d'information pour exécuter cette étape.

Prototype de la fonction | Description
void glVertexPointer(GLint taille, GLenum type, GLsizei stride, const GLvoid * pointeur); | Spécifie l'emplacement du tableau de coordonnées de vertex. Les données sont au nombre de taille par vertex, de type type et espacées de stride octets entre deux vertex.
void glColorPointer(GLint taille, GLenum type, GLsizei stride, const GLvoid * pointeur); | Spécifie l'emplacement du tableau de couleurs principales par vertex.
void glSecondaryColorPointer(GLint taille, GLenum type, GLsizei stride, const GLvoid * pointeur); | Spécifie l'emplacement du tableau de couleurs secondaires par vertex.
void glIndexPointer(GLenum type, GLsizei stride, const GLvoid * pointeur); | Spécifie l'emplacement du tableau de couleurs indexées par vertex.
void glNormalPointer(GLenum type, GLsizei stride, const GLvoid * pointeur); | Spécifie l'emplacement du tableau des normales par vertex.
void glFogCoordPointer(GLenum type, GLsizei stride, const GLvoid * pointeur); | Spécifie l'emplacement du tableau des coordonnées de brouillard par vertex.
void glTexCoordPointer(GLint taille, GLenum type, GLsizei stride, const GLvoid * pointeur); | Spécifie l'emplacement du tableau des coordonnées de texture par vertex. Dans le cas de Multitexturage, les coordonnées correspondent à la texture active
void glEdgeFlagPointer(GLsizei stride, const GLvoid * pointeur); | Spécifie l'emplacement du tableau d'indicateur de contours par vertex.

On peut remarquer que certaines fonctions ne prennent pas de paramètre taille car certaines informations ont un nombre de composantes fixes. Le paramètre stride peut être différent de zéro, par exemple, dans le cas d'un tableau unique pour les couleurs primaires et secondaires.

Une fois que nous avons indiqué où se situaient toutes les informations de notre géométrie, il existe donc plusieurs façons de décrire notre géométrie grâce à différentes fonctions.

Prototype de la fonction | Description
void glArrayElement(GLint index); | Doit être placé entre glBegin et glEnd. Récupère les informations du vertex d'index index dans tous les tableaux activés
void glDrawElements(GLenum mode, GLsizei taille, GLenum type, void * indices); | Crée automatiquement une géométrie avec le type de primitive mode, composée des vertex des taille index contenu dans le tableau indices de type type
void glMultiDrawElements(GLenum mode, GLsizei taille, GLenum type, void ** indices, GLsizei taille_primitive); | Correspond à l'appel de taille_primitive fois la fonction glDrawElements où indice est un tableau à 2 dimensions. Chaque sous-tableau est passé en paramètre à chaque appel.
void glDrawArrays(GLenum mode, GLint premier, GLsizei taille); | Utilise l'ensemble des vertex des différents tableaux pour dessiner la géométrie utilisant le type de primitive mode et les taille indices en commençant par le premier
void glMultiDrawArrays(GLenum mode, GLint * premier, GLsizei * taille, GLsizei taille_primitive); | Correspond à l'appel de taille_primitive fois la fonction glDrawArrays où premier et taille contiennent les paramètres de chaque appel.

glArrayElement correspond au mode "Immédiat" donc donne des performances identiques.

On peut remarquer deux types de fonctions : gl*Elements qui récupèrent chaque vertex indépendamment dans les tableaux d'informations, gl*Arrays qui utilisent l'ensemble des vertex des tableaux. Pour utiliser un tableau unique entrelacé, on utilise la fonction glInterleavedArrays.

III-B. Complément par les "Display List"

Nous avons vu que les commandes OpenGL pouvaient être nombreuses et surtout répétitives entre deux frames. OpenGL offre la possibilité de pallier à ce problème par l'enregistrement de commandes successives précompilées. Cette technologie, appelée "Display List", est tout à fait utilisable pour le dessin immédiat ainsi que pour les Vertex Array. Le principe d'utilisation des listes est d'abord de créer un identifiant par liste, de créer la liste et de l'appeler à chaque fois que l'on en a besoin.

Prototype de la fonction | Description
GLuint glGenLists(GLsizei nombre); | Crée nombre identifiants séquentiels de listes d'affichages disponibles.
void glNewList(GLuint index, GLenum mode); | Spécifie le début de la création de la liste d'identifiant index où mode est défini parmi GL_COMPILE, qui compile seulement la liste et GL_COMPILE_AND_EXECUTE, qui compile et exécute la liste.
void glEndList(void); | Spécifie la fin de la liste courante.
void glCallList(GLuint index); | Appelle les commandes de la liste index précompilée.
void glCallLists(GLsizei nombre, GLenum type, const GLvoid * liste); | Appelle les commandes successives des lites dont les nombre identifiants sont de type type dans le tableau liste.
void glDeleteLists(GLuint premiere, GLsizei nombre); | Supprime les nombre commandes successives en commençant par l'identifiant premiere.

III-C. Pour notre exemple

L'exemple du cube est très simple pour appliquer tout ce que nous avons vu précédemment. Nous allons donc définir nos 8 vertex dans des tableaux de vertex et de couleurs. Nous allons ensuite définir un tableau des index successifs des vertex à utiliser pour des primitives de type GL_TRIANGLES. Nous choisissons ici le cas simple sans "Display List" et avec la fonction glDrawElements pour optimiser la quantité d'information envoyée à OpenGL.

Nous remarquons tout de suite que le nombre d'appels de fonction a bien été réduit puisqu'il passe de 74 pour le mode immédiat à 7 pour les vertex array. De plus, la quantité d'information a diminué car elle est passée de 216 pour le mode immédiat à 84 ( 8 vertex * 3 composantes * 2 informations + 36 indices ) valeurs pour les vertex array. Par contre, reste le problème que toutes les informations sont envoyées à chaque frame de notre application alors que ce ne serait pas nécessaire pour une géométrie statique (sauf par l'utilisation des Display List).

Rendu d'un cube avec VertexArray
Sélectionnez

GLfloat VertexArray[24] = {
    -1.0f, 1.0f, -1.0f,
    -1.0f, -1.0f, -1.0f,
    -1.0f, 1.0f, 1.0f,
    -1.0f, -1.0f, 1.0f,
    1.0f, 1.0f, 1.0f,
    1.0f, -1.0f, 1.0f,
    1.0f, 1.0f, -1.0f,
    1.0f, -1.0f, -1.0f
};

GLfloat ColorArray[24] = {
    1.0f, 0.0f, 0.0f,
    1.0f, 0.0f, 1.0f,
    1.0f, 1.0f, 1.0f,
    0.0f, 0.0f, 1.0f,
    0.0f, 1.0f, 0.0f,
    0.0f, 1.0f, 1.0f,
    1.0f, 1.0f, 0.0f,
    1.0f, 1.0f, 1.0f
};

GLuint IndiceArray[36] = {
    0,1,2,2,1,3,
    4,5,6,6,5,7,
    3,1,5,5,1,7,
    0,2,6,6,2,4,
    6,7,0,0,7,1,
    2,3,4,4,3,5
};

glEnableClientState( GL_VERTEX_ARRAY );
glEnableClientState( GL_COLOR_ARRAY );

glVertexPointer( 3, GL_FLOAT, 0, VertexArray );
glColorPointer( 3, GL_FLOAT, 0, ColorArray );

glDrawElements( GL_TRIANGLES, 36, GL_UNSIGNED_INT, IndiceArray );

glDisableClientState( GL_COLOR_ARRAY );
glDisableClientState( GL_VERTEX_ARRAY );

III-D. Avantages / Inconvénients de cette technique

Nous avons pu voir que les techniques des "Vertex Array" et des "Display List" pouvaient grandement améliorer le rendu de géométrie avec OpenGL. Pour les "Vertex Array", nous avons vu qu'il en existait deux types : sans ou avec partage de vertex (glDrawArrays vs glDrawElements). La version sans partage de vertex (glDrawArrays) a été déconseillée par Nvidia ainsi que par ATI. Il est préférable d'utiliser la version glDrawElements qui permet de créer un cache de vertex uniques et, grâce à un tableau d'indices, de partager le vertex pour une même géométrie. Nous pouvons en déduire un certain nombre d'avantages :

  • Réduction du nombre d'appels de fonction
  • Réduction de la quantité d'information envoyée à la carte graphique
  • Précompilation d'une série de commandes OpenGL

A cela, nous pouvons ajouter quelques optimisations possibles qu'offrent les "Vertex Array" :

  • Utilisation du type GL_UNSIGNED_SHORT (16bit) à la place du type GL_UNSIGNED_INT (32bit) (optimisation mémoire et temps de transfert)
  • Utilisation de GL_TRIANGLE_STRIP (1/3 d'indices en moins en moyenne)
  • Dans le cas d'information couleur, il est conseillé d'utiliser GL_UNSIGNED_BYTE ou GL_FLOAT (optimisation de conversion des données)
  • Dans le cas d'information de normale, il est conseillé d'utiliser GL_FLOAT, mais déconseillé d'utiliser GL_BYTE.

Malgré la résolution de certains problèmes par rapport au dessin "immédiat", on conserve certains inconvénients avec cette technique. Voici une liste des problèmes les plus remarquables :

  • Espace mémoire occupé par les "Display List" compilées
  • Temps de compilation des "Display List"
  • Applicable uniquement pour une géométrie statique

Malgré tous les gains apportés par ces nouvelles technologies, il reste quelques problèmes assez importants. Tout d'abord, les "Display List" permettent de précompiler des commandes OpenGL mais cette précompilation a un surcoût mémoire ainsi qu'un surcoût en temps. Le surcoût mémoire provient de l'enregistrement des commandes elles mêmes, l'idéal serait de stocker uniquement les informations de notre géométrie. De plus, ces deux techniques associées obligent à dessiner uniquement une géométrie statique non-modifiable. Si on utilise uniquement les "VertexArray" sans les "DisplayList", le problème de l'envoi répétitif des informations (comme dans notre exemple) réapparaît. OpenGL propose donc une solution à tout cela et cette solution s'appelle les "Vertex Buffer Object".

IV. Nouvelle technologie : les Vertex Buffer Object (VBO)

IV-A. Par où commencer ?

Nous avons donc vu que les "VertexArray", accompagnés des "DisplayList", étaient une grande avancée dans le rendu de géométrie. Mais il persiste quelques problèmes assez importants notamment dans les surcoûts mémoire et temps de ces technologies. Pour cela, après l'apparition des extensions NV_vertex_array_range et ATI_vertex_array_object par, respectivement, Nvidia et ATI et l'approbation par l'ARB (Architecture Review Board) de l'extension ARB_vertex_buffer_object, a été créé la technologie des VertexBufferObject (VBO). C'est ainsi que depuis les spécifications de la version 1.5 d'OpenGL est intégrée cette technologie qui permet de stocker directement en mémoire graphique toute notre géométrie à dessiner. Grace à un procédé proche des "VertexArray", les VBO permettent d'envoyer et de stocker toutes les informations de notre géométrie dans notre carte graphique. Ce principe, utilisé pour les textures, est maintenant utilisé pour la géométrie.

Pour utiliser ces buffers, il faut d'abord commencer par demander à OpenGL un ou plusieurs identifiants pour ces buffers. Cet identifiant sera utilisé pour signaler à OpenGL que nous voulons utiliser le buffer de la géométrie correspondante. Nous pouvons, bien évidemment, utiliser et enregistrer plusieurs buffers pour notre application, tant qu'il y a suffisamment de mémoire dans la carte graphique.

Prototype de la fonction | Description
void glGenBuffers(GLsizei nombre, Guint * buffers); | Crée nombre identifiants de buffer et les place dans le bloc mémoire commençant par buffers (élément simple ou tableau)
void glBindBuffer(GLenum type, GLuint buffer); | Spécifie que l'on utilise le buffer d'identifiant buffer comme buffer courant et qu'il est de type type. type est à choisir parmi GL_ARRAY_BUFFER et GL_ELEMENT_ARRAY_BUFFER.
void glDeleteBuffers(GLsizei nombre, const GLuint * buffers); | Supprime les nombre buffers de la liste buffers.

GL_ARRAY_BUFFER correspond aux tableaux des informations sur les vertex et GL_ELEMENT_ARRAY_BUFFER correspond aux tableaux des indices de vertex.

L'étape suivante est de remplir notre buffer avec les informations sur nos vertex. Nous allons donc envoyer des tableaux de données à notre carte graphique qui va remplir sa mémoire comme elle le ferait pour une texture. C'est dans cette partie que nous allons définir quel type d'utilisation nous allons faire de notre buffer par l'intermédiaire du paramètre utilisation.

Prototype de la fonction | Description
void glBufferData(GLenum type, GLsizeiptr taille, const GLvoid * donnees, GLenum utilisation); | Crée et initialise le buffer actif de type type avec les taille informations contenues dans le tableau donnees. L'utilisation de ce buffer sera définie par utilisation
void glBufferSubData(GLenum type, GLintptr decalage, GLsizeiptr taille, const GLvoid * donnees); | Met à jour le buffer de type type à partir de la zone commençant par decalage avec les taille informations contenues dans donnees.

IV-B. Différents cas d'utilisations

Les VBO supporte différents types d'utilisations qui peuvent être différents pour chaque buffer. Les cas d'utilisations se définissent par deux critères qui sont la fréquence avec laquelle nous allons mettre à jour les informations et la façon dont nous allons accéder aux données. Pour chacun des paramètres, il existe trois catégories différentes. Dans le cas de la fréquence d'utilisation, elle peut être :

  • STATIC : les données seront modifiées une fois pour de nombreuses utilisations
  • STREAM : les données seront modifiées une fois pour quelques utilisations
  • DYNAMIC : les données seront modifiées et utilisées régulièrement de nombreuses fois

Une fois que nous avons déterminé cette fréquence, il faut choisir un type d'utilisation de notre buffer. Il existe trois types différents à choisir parmi :

  • DRAW : les données sont modifiées par l'application et utilisées par OpenGL pour le rendu d'images (le plus utilisé)
  • READ : les données sont modifiées par une lecture de données depuis OpenGL et utilisées en lecture par l'application.
  • COPY : les données sont modifiées par une lecture de données depuis OpenGL et utilisées par OpenGL pour le rendu d'images

A partir de ces deux paramètres, nous pouvons déterminer quel type de buffer nous allons utiliser et quel paramètre passer à la fonction glBufferData. Nous avons donc neuf possibilités, donc neuf valeurs différentes :

 | STATIC | STREAM | DYNAMIC
DRAW | GL_STATIC_DRAW | GL_STREAM_DRAW | GL_DYNAMIC_DRAW
READ | GL_STATIC_READ | GL_STREAM_READ | GL_DYNAMIC_READ
COPY | GL_STATIC_COPY | GL_STREAM_COPY | GL_DYNAMIC_COPY

Ce paramètre est très important car OpenGL va se charger seul d'organiser sa mémoire en fonction du type d'utilisation. Les utilisations du type GL_*_DRAW sont les plus utilisées pour dessiner une géométrie car les informations proviennent de l'application et non d'OpenGL. Ensuite, les utilisations de type GL_STATIC_* vont plutôt s'appliquer à une géométrie qui ne va jamais être modifiée au cours de l'application comme des bâtiments, des statues, ... Les utilisations GL_STREAM_* vont s'appliquer généralement à une géométrie qui est modifiée une seule fois juste avant un rendu. Ceci est très proche du type GL_DYNAMIC_* qui est prévu pour de nombreuses modifications de données avant une seule utilisation. Ces deux dernières utilisations peuvent s'appliquer, par exemple, à des applications d'animations de personnages (skinning), à des objets déformables par des simulations physiques, ...

IV-C. Utiliser un buffer

Maintenant que nous avons créé notre buffer, il faudrait pouvoir le modifier et l'utiliser tout au long de notre application. OpenGL offre la possibilité d'accéder directement aux données du buffer actif. Cet accès se fait par les deux fonctions :

Prototype de la fonction | Description
void * glMapBuffer(GLenum type, GLenum acces); | Retourne le pointeur vers les données du buffer courant de type type autorisant un accès de type acces parmi GL_READ_ONLY (lecture seule), GL_WRITE_ONLY (écriture seule), et GL_READ_WRITE (lecture et écriture).
void glUnmapBuffer(GLenum type); | Signifie à OpenGL que l'accès au buffer courant de type type est terminé.

Pour utiliser les données contenues dans le buffer, nous allons utiliser les mêmes fonctions que pour les "Vertex Array". Il faut donc commencer par activer le buffer correspondant à notre géométrie grâce à la fonction glBindBuffer. Ainsi les fonctions glVertexPointer, glColorPointer, glSecondaryColorPointer, glNormalPointer, glFogCoordPointer, glTexCoordPointer et glEdgeFlagPointer vont-elles être utilisées pour les buffers de type GL_ARRAY_BUFFER. La seule différence sera qu'aucun tableau de données ne sera utilisé mais sera remplacé par un décalage mémoire dans notre buffer par rapport à son début. Nous pouvons donc créer un VBO par type d'information et par géométrie mais également un seul VBO pour toutes les informations de notre géométrie. Voici quelques exemples d'utilisation :

Quelques exemples
Sélectionnez

glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), 0);
glColorPointer(3, GL_FLOAT, 3 * sizeof(float), ((void*)NULL + (3)));

IV-D. Pour notre exemple

Nous allons donc étudier notre exemple. Nous devons dessiner un cube coloré qui ne changera jamais. Aucune information ne sera modifiée ainsi nous pourrons utiliser un buffer de type GL_STATIC_DRAW. Nous allons donc stocker toutes nos informations dans un tableau unique qui sera stocké une seule fois à l'initialisation :

Initialisation de notre VBO
Sélectionnez

GLfloat CubeArray[48] = {
    1.0f, 0.0f, 0.0f, -1.0f, 1.0f, -1.0f,
    1.0f, 0.0f, 1.0f, -1.0f, -1.0f, -1.0f,
    1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f,
    0.0f, 0.0f, 1.0f, -1.0f, -1.0f, 1.0f,
    0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f,
    0.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f,
    1.0f, 1.0f, 0.0f, 1.0f, 1.0f, -1.0f,
    1.0f, 1.0f, 1.0f, 1.0f, -1.0f, -1.0f
};

GLuint IndiceArray[36] = {
    0,1,2,2,1,3,
    4,5,6,6,5,7,
    3,1,5,5,1,7,
    0,2,6,6,2,4,
    6,7,0,0,7,1,
    2,3,4,4,3,5
};

// Génération des buffers
glGenBuffers( 2, CubeBuffers );

// Buffer d'informations de vertex
glBindBuffer(GL_ARRAY_BUFFER, CubeBuffers[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(CubeArray), CubeArray, GL_STATIC_DRAW);

// Buffer d'indices
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, CubeBuffers[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(IndiceArray), IndiceArray, GL_STATIC_DRAW);

Une fois créé, nous pouvons l'utiliser à chaque frame. Nous devons spécifier les buffers à utiliser ainsi que l'emplacement de chaque information.

Rendu du cube avec notre VBO
Sélectionnez

// Utilisation des données des buffers
glBindBuffer(GL_ARRAY_BUFFER, CubeBuffers[0]);
glVertexPointer( 3, GL_FLOAT, 6 * sizeof(float), ((float*)NULL + (3)) );
glColorPointer( 3, GL_FLOAT, 6 * sizeof(float), 0 );

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, CubeBuffers[1]);

// Activation d'utilisation des tableaux
glEnableClientState( GL_VERTEX_ARRAY );
glEnableClientState( GL_COLOR_ARRAY );

// Rendu de notre géométrie
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);

glDisableClientState( GL_COLOR_ARRAY );
glDisableClientState( GL_VERTEX_ARRAY );

Nous pouvons remarquer que d'un point de vue mémoire, seules les informations de notre géométrie seront stockées contrairement aux "Display List" qui enregistrent également toute la succession des commandes OpenGL. De plus, le temps demandé par la compilation des "Display List" n'est plus nécessaire car on se contente d'envoyer directement notre géométrie. Enfin, grâce au mapping de buffer, on peut uniquement modifier les données des géométries dynamiques et nous ne sommes plus obligés de nous contenter des formes statiques.

Conclusion

Nous avons donc vu les différentes optimisations qu'a apportées OpenGL au rendu de géométrie. Chacune des techniques a ses avantages propres mais les "Vertex Array" et les "Vertex Buffer Object" ont pour objectif de combler les lacunes des précédentes. A titre d'exemple, sur une carte Nvidia GeForce 7600 GS, les dessins de notre cube nous donnent les performances suivantes :

Immédiat | Vertex Array | Vertex Buffer Object
1445 FPS | 1485 FPS | 1515 FPS


Le guide du routard dans OpenGL 1

Vu dans ... Le guide du routard dans OpenGL 1.ZIP

Le guide du routard dans OpenGL 1.2

Orion (F. Gasser)

Gritche (J-F. Herouard)

Sommaire

1. Introduction______________________________________________________________________ 4

1.1. Qu’est ce que OpenGL__________________________________________________________ 4

1.2. Les bonnes adresses____________________________________________________________ 5

1.3. Les bons documents____________________________________________________________ 5

2. GLUT, l’OpenGL Utility Toolkit_______________________________________________________ 5

2.1. Un exemple__________________________________________________________________ 6

2.2. L’initialisation________________________________________________________________ 6

2.3. Les callbacks_________________________________________________________________ 7

2.4. Travail en background_________________________________________________________ 7

2.5. Les fonctions de dessin 3D évoluées_______________________________________________ 7

2.6. Exécuter le programme________________________________________________________ 8

3. Initialisation de la vue______________________________________________________________ 8

3.1. Exemple d’initialisation 2D______________________________________________________ 8

3.2. Présentation des matrices d’OpenGL______________________________________________ 8

4. Dessiner__________________________________________________________________________ 9

4.1. Un classique : le triangle________________________________________________________ 9

4.2. Les Buffers__________________________________________________________________ 9

4.3. Bouger le triangle, utilisation de la matrice de vue__________________________________ 12

5. Dessiner en 3D____________________________________________________________________ 13

5.1. Initialisation________________________________________________________________ 13

5.2. Exemple : un Cube____________________________________________________________ 14

5.3. Depth-Buffer________________________________________________________________ 15

6. Paramètres du rendering____________________________________________________________ 15

6.1. Antialiasing_________________________________________________________________ 15

6.2. Culling_____________________________________________________________________ 15

7. Eclairage_______________________________________________________________________ 16

7.1. Activation__________________________________________________________________ 16

7.2. Paramètres_________________________________________________________________ 17

7.3. Fonctions OpenGL___________________________________________________________ 18

7.4. Les normales________________________________________________________________ 20

7.5. Exemple____________________________________________________________________ 21

8. Fog (brouillard)__________________________________________________________________ 22

8.1. Le fonctionnement du brouillard________________________________________________ 22

8.2. L’utilisation du brouillard_____________________________________________________ 22

8.3. Les équations________________________________________________________________ 22

9. Transparence et alpha-blending______________________________________________________ 23

9.1. Activation__________________________________________________________________ 23

9.2. Exemple____________________________________________________________________ 23

9.3. Paramètres_________________________________________________________________ 23

9.4. Ordre des primitives et depth-buffer______________________________________________ 24

10. Listes________________________________________________________________________ 25

10.1. Commandes OpenGL_______________________________________________________ 25

10.2. Exemple_________________________________________________________________ 25

11. Textures______________________________________________________________________ 26

11.1. Principe général___________________________________________________________ 26

11.2. En OpenGL_______________________________________________________________ 26

11.3. Exemple_________________________________________________________________ 28

11.4. MipMapping______________________________________________________________ 29

11.5. Chargement dans la carte vidéo_______________________________________________ 29

11.6. Exemple_________________________________________________________________ 30

11.7. Priorités des textures_______________________________________________________ 30

12. Les opérations sur les pixels_______________________________________________________ 31

12.1. Introduction______________________________________________________________ 31

12.2. La Raster Position_________________________________________________________ 31

12.3. Dessiner le bitmap__________________________________________________________ 31

12.4. Lire, écrire et copier des images_______________________________________________ 32

12.5. Agrandissement, réduction et réflexion_________________________________________ 33

13. Tests de validation et stencil buffer__________________________________________________ 33

13.1. Chaîne de tests_____________________________________________________________ 33

13.2. Scissor test________________________________________________________________ 33

13.3. Alpha test________________________________________________________________ 33

13.4. Stencil test________________________________________________________________ 33

14. Ombres et reflets________________________________________________________________ 35

14.1. Reflets___________________________________________________________________ 35

14.2. Ombres__________________________________________________________________ 38

15. Courbes et Surfaces calculées______________________________________________________ 41

15.1. Présentation______________________________________________________________ 41

15.2. Quadrics_________________________________________________________________ 41

15.3. Bézier___________________________________________________________________ 42

16. Ce qui n’a pas de place ailleurs____________________________________________________ 45

16.1. Dessiner deux fois au même endroit____________________________________________ 45

16.2. Clipping_________________________________________________________________ 45

16.3. Multitexturing_____________________________________________________________ 46

1. Introduction

Qu’est ce que OpenGL

OpenGL est une interface de programmation vers le matériel graphique. Cette interface compte environ 150 commandes qui sont utilisées dans les programmes interactifs en 3D. Etant donné que cette API est à l’origine professionnelle, il y a beaucoup de fonctions qui ne sont toujours pas câblées sur les cartes accélératrices 3D pour le grand public, et qui seront donc émulées en software.

La première particularité d’OpenGL est que la programmation est indépendante du matériel graphique utilisé, et que son domaine d’utilisation n’est pas confiné aux PC/Windows ou Mac. A l’origine développé par Silicon Graphics, OpenGL est aujourd’hui disponible sur les plates-formes x86 et 68000 entre autres, que ce soit sous Windows, Linux ou Mac. La conséquence immédiate est qu’aucune commande de fenêtrage, ou d’une manière générale toute commande dépendant de l’OS, n’est incluse et que seuls des commandes graphiques de bas niveaux existent (dessiner un point, un segment mais pas de sphère par exemple). Tout objet complexe est composé à partir d’un nombre limité de primitives graphiques.

Deuxièmement, OpenGL se base toujours sur d’autres librairies graphiques qui sont en général spécifiques au système d’exploitation ou qui apportent des fonctions complexes par dessus la librairie OpenGL. L’OpenGL Utility Library (GLU) ainsi que l’OpenGL Utility Toolkit (GLUT) en sont des exemples.

Une autre particularité est que certaines implémentations d’OpenGL sont conçues pour que la station sur laquelle on affiche ne soit pas celle sur laquelle tourne le programme. Cette fonctionnalité n’est utilisé que sur les systèmes basés sur l’X, et ne sera donc pas détaillée ici (en gros, cela est possible car OpenGL utilise une architecture client (un programme) – serveur (la carte vidéo et son driver OpenGL)).

Silicon Graphics n’a pas implémenté de version pour Linux de OpenGL. Il convient donc d’utiliser Mesa, qui est une librairie compatible OpenGL (attention, la compatibilité n’est ni garantie ni totale, .... Gritche tire une sale gueule la parce qu’il est sûr que Mesa 3.1 est compatible OpenGL 1.2, il dit aussi que ça s’appelle Mesa parce que l’emploi du nom OpenGL™ n’est pas possible en GNU).

Les bonnes adresses

Voici quelques bonnes adresse à visiter pour avoir accès à des informations complémentaires et aux manuels de référence :

· OpenGL :

http://www.opengl.org : news, documentation, liens…

http://trant.sgi.com/opengl : des exemples, des astuces

http://reality.sgi.com/opengl : idem

· GLU :

http://www.opengl.org/Documentation/GLX.html

· GLUT :

http://reality.sgi.com/mjk/spec3/spec3.html

· Mesa :

http://www.mesa3d.org

· AGL (Extension OpenGL pour Apple) :

http://www.sd.tgs.com/Products/opengl.htm

· PGL (Extension OpenGL pour IBM OS/2 Warp

http://www.austin.ibm.com/software/OpenGL/

Sur la machine de Game, il y a déjà dans les 250 Mo de download provenant de ces sites, alors avant de dans lancer un wget sauvage à l’école, vérifiez :-). En plus les exemples sont déjà compilés.

Si vous trouvez quelque chose d’intéressant qui n’est pas sur la machine, n’hésitez pas à en faire part à tout le monde.

Les bons documents

Il y a quelques documents de références en ligne qui sont bons à avoir : opengl1.2.pdf, glu1.3.pdf, glut.spec.ps et les man pages d’OpenGL en Html (une page html par fonction).

2. GLUT, l’OpenGL Utility Toolkit

OpenGL ne s’occupant pas des opérations relatives au système ou au fenêtrage, il laisse la place à d’autres API, dont GLUT fait parti. Il existe des implémentations de GLUT pour Windows NT, X-Window et OS/2, ce qui permet de compiler le même code source sur toutes les plates-formes.

GLUT est une interface entre le programme et l’humanoïde qui se trouve derrière l’écran, la souris, ou le joystick. Sa programmation est de type événementielle et chaque agitation de l’humain est considéré comme un événement qui sera géré par des fonctions personnalisées (callback).

Un exemple

Voici un premier exemple de programme utilisant OpenGL :

#include <GL/glut.h>

int main(int argc, char** argv)

{

Glint Xscreen = 800;

Glint Yscreen = 600;

glutInit(&argc, argv);

glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);

glutInitWindowSize (Xscreen, Yscreen);

glutCreateWindow (argv[0]);

glutDisplayFunc (display);

glutReshapeFunc (reshape);

glutIgnoreKeyRepeat(1);

glutKeyboardFunc(keyboard);

glutKeyboardUpFunc(keyboardup);

glutSpecialFunc(special);

glutSpecialUpFunc(specialup) ;

glutMouseFunc(mouse);

glutMotionFunc(motion);

glutPassiveMotionFunc(motion);

glutIdleFunc (IntelligenceArtificielle);

glutMainLoop ();

return(0);

}

L’initialisation

On remarque que l’on fait plusieurs appels à des fonctions GLUT pour créer une fenêtre. Dans l’ordre, on a :

· glutInit(int *argc, char **argv) : A être appelé avant tout autre fonction GLUT. Cette fonction gere sous X les paramètres tels que –display et –geometry.

· glutInitDisplayMode(unsigned int mode) : différencie les mode RGBA et couleur indexé, ainsi que le double-buffering et les buffers à utiliser (depth, stencil, accumulation), que nous étudierons par la suite

· glutInitWindowPostion(int x, int y) : no comment

· glutInitWindowSize(int width, int size) : no comment

· int glutCreatWindow(char *string) : crée la fenêtre (et un contexte OpenGL) avec tous les paramètres spécifiés avant. Elle renvoie un identifiant unique pour la nouvelle fenêtre. Attention : la fenêtre ne sera pas affichée avant l’appel à glutMainLoop()

Les callbacks

Viennent ensuite les « callback ». Il s’agit de spécifier quels sont les fonctions à appeler suivant les événements qui se produisent. On peut changer les fonctions appelées à n’importe quel moment. Si on spécifie la valeur NULL comme adresse de fonction à utiliser, cela a pour effet de désactiver le callback correspondant. Elles sont par défaut à NULL. Les plus utilisés des callback sont :

· glutDisplayFunc(void(*func)(void)) : indique la fonction à utiliser lors de l’affichage graphique.

· glutReshapeFunc(void(*func)(int w, int h)) : si la fenêtre est redimentionnée

· glutKeyboardFunc(void(*func)(unsigned char key, int x, int y)) et glutMouseFunc(void(*func)(int button, int state, int x, int y)) : événement clavier et souris

· glutMotionFunc(void (*func)(int x, int y))

Travail en background

Lorsque le programme n’a plus rien a faire, ie l’affichage a été traité, et les callback aussi, on peut spécifier de rentrer dans un fonction qui peut se charger par exemple de faire évoluer le monde virtuel. On utilise alors la fonction :

· GlutIdleFunc(void (*func)(void)) : comme d’hab, NULL passé en paramètre désactive cette fonction.

Les fonctions de dessin 3D évoluées

GLUT peut se charger de dessiner des objets 3D centrés sur l’origine en mode fil de fer (wire) ou solide (solid). Les objets sont :

cone icosahedron teapot

cube octahedron tetrahedron

dodecahedron sphere torus

Pour dessiner un cube ou une sphère, on utilise par exemple les fonctions :

void glutWireCube(Gldouble size) ;

void glutSolidCube(Gldouble size) ;

void glutWireSphere(Gldouble radius, Glint slices, Glint stacks) ;

void glutSolidSphere(Gldouble radius, Glint slices, Glint stacks) ;

Exécuter le programme

Pour exécuter le programme et voir réellement quelque chose s’afficher après l’initialisation, il faut appeler la fonction :

· glutMainLoop(void) Ici, on rentre dans la boucle infinie d’exécution. Ca doit être à peut de choses prêt la dernière ligne du programme. On ne pourra sortir de cette boucle que par un exit().

3. Initialisation de la vue

Exemple d’initialisation 2D

OpenGL est plus connu pour son utilisation 3D, mais il est aussi prévu pour fonctionner en 2D. Comme c’est un peu plus facile à appréhender, on commence par là.

Une fois que le « contexte » OpenGL est crée, il reste à créer la vue. Cela se fait ainsi :

GLsizei width = cx, height = cy; // width, height : taille de la fenêtre

GLdouble aspect;

if (cy == 0)

aspect = (GLdouble)width;

else

aspect = (GLdouble)width/(GLdouble)height;

glMatrixMode(GL_PROJECTION); // initialisation de la matrice de projection

glLoadIdentity();

gluOrtho2D(-1.0f * aspect, 1.0f * aspect, -1.0f, 1.0f);

glMatrixMode(GL_MODELVIEW); // initialisation de la matrice de vue

glLoadIdentity();

Il s’agit ici d’une initialisation d’une vue 2D.

Présentation des matrices d’OpenGL

En OpenGL, toutes les transformations géométriques sont entrées sous forme de matrice, la vue est définie par la position de la caméra (matrice de changement de repère) et par la matrice de projection.

La matrice de projection s’appelle GL_PROJECTION. Il n’est possible d’accéder qu’à une matrice à la fois, donc pour modifier une matrice, il faut d’abord l’activer avec glMatrixMode. La matrice choisie devient la « matrice courante ». Il reste à définir la matrice de projection, mais inutile de se revoir le cours de prépa, car heureusement des fonctions de glu permettent de spécifier les matrices les plus courantes. La fonction

void gluOrtho2D( GLdouble left, GLdouble right, GLdouble bottom, GLdouble top )

permet dans l’exemple de mettre une matrice de projection 2D de la dimension voulue.

GlLoadIdentity initialise la matrice à l’identité.

La matrice GL_MODELVIEW est en quelque sorte la matrice de positionnement de la vue. C’est le plus souvent une matrice de changement de repère. Il faut la considérer comme une matrice qui positionne les objets dans le repère du monde, et éviter de la considérer comme la position de la caméra, même si c’est aussi son rôle.

Il existe une troisième matrice, GL_TEXTURE, mais celle-ci n’est jamais utilisée directement.

Toutes sont des matrices 4x4 de float.

4. Dessiner

Un classique : le triangle

Les fonctions suivantes permettent de dessiner un triangle à l’écran :

glClear(GL_COLOR_BUFFER_BIT); // efface l’écran

glBegin(GL_POLYGON); // début d’un dessin d’un polygone

glColor3f(1.0f, 0.0f, 0.0f); // changement de couleur

glVertex2f(-0.5f, -0.5f); // entrée d’une coordonnée

glColor3f(0.0f, 1.0f, 0.0f);

glVertex2f( 0.5f, -0.5f);

glColor3f(0.0f, 0.0f, 1.0f);

glVertex2f( 0.5f, 0.5f);

glEnd(); // fin du polygone

glFlush(); // affichage

Ce qui donne (imprimé en noir & blanc c’est moche, mais tant pis) :

Les Buffers

Il existe dans OpenGL des « buffers », qui d’une manière générale ne sont que des espaces mémoires contenant des données propres à l’OpenGL.

Le principal est le « COLOR_BUFFER », qui correspond à ce que l’on voit sur l’écran.

La fonction glClear permet de remplir tout un buffer avec la même valeur. La valeur par défaut est 0. Un appel à glClear sur le COLOR_BUFFER rempli l’écran de noir. GlClearColor permet de changer la couleur de remplissage.

Un autre buffer, le back-buffer, fonctionne différemment : pour ne pas voir se dessiner la scène à l’écran, on utilise la technique du « double-buffering ». Le COLOR_BUFFER est en fait dédoublé : l’un est affiché (front-buffer), l’autre est celui sur lequel on travaille (back-buffer). A chaque fois qu’une image est calculée, on inverse (swap) les buffers. Pour le programmeur, tout marche comme s’il dessinait toujours sur le même buffer. L’avantage est que l’utilisateur ne vois pas se dessiner l’image.

Les autres buffers standards sont le Depth-buffer, le Stencil-buffer, l’Accumulation-buffer. Chaque buffer joue un rôle précis, mais ils ne seront présentés que lorsque nous en aurons besoin. D’autres sont des extensions propriétaires (T-Buffer, W-Buffer, …).

Le début de l’entrée d’une primitive se fait avec glBegin : ici un polygone. Les possibilités sont (tirées de la man page de glBegin) :

GL_POINTS Treats each vertex as a single point. Vertex n defines point n.

N points are drawn.

GL_LINES Treates each pair of vertices as an independent line segment.

Vertices 2n-1 and 2n define line n. N/2 lines are drawn.

GL_LINE_STRIP

Draws a connected group of line segments from the first vertex to

the last. Vertices n and n+1 define line n. N-1 lines are

drawn.

GL_LINE_LOOP

Draws a connected group of line segments from the first vertex to

the last, then back to the first. Vertices n and n+1 define line

n. The last line, however, is defined by vertices N and 1. N

lines are drawn.

GL_TRIANGLES

Treates each triplet of vertices as an independent triangle.

Vertices 3n-2, 3n-1, and 3n define triangle n. N/3 triangles are

drawn.

GL_TRIANGLE_STRIP

Draws a connected group of triangles. One triangle is defined

for each vertex presented after the first two vertices. For odd

n, vertices n, n+1, and n+2 define triangle n. For even n,

vertices n+1, n, and n+2 define triangle n. N-2 triangles are

drawn.

GL_TRIANGLE_FAN

Draws a connected group of triangles. One triangle is defined

for each vertex presented after the first two vertices. Vertices

1, n+1, and n+2 define triangle n. N-2 triangles are drawn.

GL_QUADS Treats each group of four vertices as an independent

quadrilateral. Vertices 4n-3, 4n-2, 4n-1, and 4n define

quadrilateral n. N/4 quadrilaterals are drawn.

GL_QUAD_STRIP

Draws a connected group of quadrilaterals. One quadrilateral is

defined for each pair of vertices presented after the first pair.

Vertices 2n-1, 2n, 2n+2, and 2n+1 define quadrilateral n. N/2-1

quadrilaterals are drawn. Note that the order in which vertices

are used to construct a quadrilateral from strip data is

different from that used with independent data.

GL_POLYGON

Draws a single, convex polygon. Vertices 1 through N define this

polygon.

glVertex permet d’entrer un point.

Le format général des fonctions d’OpenGL est : {nom}{1234}{b s i d ub us ui}{v}, respectivement le nom de la fonction, le nombre de paramètres, leur type, passé directement ou par pointeur (vecteur). Tous les caractéristiques de la fonction sont optionnelles sauf le nom.

glVertex existe par exemple sous les formes glVertex2d, glVertex2f, glVertex2i, glVertex2s, glVertex3d, glVertex3f, glVertex3i, glVertex3s, glVertex4d, glVertex4f, glVertex4i, glVertex4s, glVertex2dv, glVertex2fv, glVertex2iv, glVertex2sv, glVertex3dv, glVertex3fv, glVertex3iv…

glColor fixe la couleur courante. De même elle existe sous de nombreuses formes.

glEnd indique la fin de l’entrée de coordonnées.

Cependant jusqu’ici rien n’est affiché : OpenGL a mis tout ce qu’il y a à faire dans un buffer, mais ne l’affiche que lorsqu’on lui demande, avec glFlush, qui rend la main immédiatement, ou avec glFinish, qui attend que tout soit affiché (peu d’intêret).

Bouger le triangle, utilisation de la matrice de vue

Rotation et translation

Pour bouger la « caméra » ou un objet (c’est la même chose de bouger tout les objets ou la caméra), il faut utiliser la matrice de vue (GL_MODELVIEW).

Comme calculer directement une matrice de changement de repère n’est pas vraiment évidente, il existe des fonctions prédéfinies : glRotate et glTranslate.

void glRotated( GLdouble angle, GLdouble x, GLdouble y, GLdouble z )

void glRotatef( GLfloat angle, GLfloat x, GLfloat y, GLfloat z )

void glTranslated( GLdouble x, GLdouble y, GLdouble z )

void glTranslatef( GLfloat x, GLfloat y, GLfloat z )

Ces fonctions multiplient à droite la matrice de vue. Il faut donc faire attention à l’ordre dans lequel les transformations sont entrées : c’est la dernière transformation entrée qui sera appliquée en premier.

Rappels :

· soient f de matrice L, g de matrice M et h de matrice N. h°g°f a pour matrice NML donc il faut entrer les matrices dans l'ordre N,M,L

· les rotations et les translations ne sont pas commutatives

Par exemple, pour faire tourner un objet sur lui-même, il faut entrer en dernier la matrice de rotation. Pour simplifier, l’origine du repère local d’un objet est par convention son centre de rotation (d’inertie) pour permettre de le faire tourner sur lui-même sans calcul complexe.

Tout cela peut paraître compliqué, mais un ordinateur ne sait comprendre que les matrices, et un énorme avantage est le temps de calcul : même si de nombreuses transformations sont entrées, il ne reste qu’une seule matrice à appliquer à tout les points entrés.

Pile de matrice

Mais comment inverser les transformations entrées ? Calculer les inverses est une perte de temps. Une pile est en effet associée à chaque matrice. Les fonctions sont :

void glPushMatrix( void )

void glPopMatrix( void )

qui respectivement empile et dépile la matrice courante. Même s’il n’y a que deux fonctions, il existe bien trois piles différentes (une par matrice). Comme pour les transformations, seule la matrice active peut-être empilée / dépilée (ce qui est logique, puisque que une matrice est empilée pour la sauvegarder avant une transformation).

Le plus souvent, un objet est caractérisé par ses angles de rotation (ax, ay, az) et sa position (x, y, z). Son affichage est alors du type :

glPushMatrix() ;

glTranslate3f(x,y,z) ;

glRotate3f(ax,ay,az) ;

glBegin(…)

…

glEnd() ;

glPopMatrix() ;

Exemple d’utilisation : un soleil avec ses satellites, et les lunes des satellites. Le principe est le même :

· Dessin du soleil

· PushMatrix

§ Translation dans le repère local d’une planète

§ Dessin de la planète

§ PushMatrix

· Translation dans le repère local d’une lune

· Dessin de la lune

§ PopMatrix

§ PushMatrix

· Translation dans le repère local d’une autre lune

· Dessin de la lune

§ PopMatrix

· PopMatrix

· PushMatrix

§ Translation dans le repère local d’une autre planète

§ Dessin de la planète

§ PushMatrix

· Translation dans le repère local d’une lune

· Dessin de la lune

§ PopMatrix

· PopMatrix

· etc.

5. Dessiner en 3D

Initialisation

Quelques changements dans l’initialisation :

glMatrixMode(GL_PROJECTION);

glLoadIdentity();

gluPerspective(45, aspect, 1.0f, 10.0f);

gluLookAt(0.0f, 0.0f, 0.0f,

0.0f, 1.0f, 0.0f,

0.0f, 0.0f, 1.0f);

glEnable(GL_DEPTH_TEST);

gluPerspective construit une matrice de projection de champ de vue de 45°, de rapport largeur/hauteur aspect, de profondeur de champ (1.0, 10.0), ie seuls les objets situés à une distance comprise entre 1.0 et 10. de la caméra sont affichés.

gluLookAt construit une matrice de changement de repère à partir de trois vecteurs : la position de la caméra, la direction où elle regarde, un vecteur indiquant le « haut » (juste la direction, il sera modifié pour faire un repère orthonormé).

Exemple : un Cube

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

total %= 360*500; // temps écoulé en ms

glPushMatrix();

glTranslatef(0.0f, 5.0f+(GLfloat)sin(GLfloat(total)/500.0f)*3.0f, 0.0f);

glRotatef(total/50.0f, 0.3f, 0.5f, 0.8f);

glBegin(GL_QUADS);

glColor3f(1.0f, 0.0f, 0.0f);

glVertex3f(-0.5f, -0.5f, -0.5f);

glVertex3f( 0.5f, -0.5f, -0.5f);

glVertex3f( 0.5f, 0.5f, -0.5f);

glVertex3f(-0.5f, 0.5f, -0.5f);

glColor3f(1.0f, 1.0f, 0.0f);

glVertex3f(-0.5f, -0.5f, 0.5f);

glVertex3f( 0.5f, -0.5f, 0.5f);

glVertex3f( 0.5f, 0.5f, 0.5f);

glVertex3f(-0.5f, 0.5f, 0.5f);

glColor3f(0.0f, 0.0f, 1.0f);

glVertex3f( 0.5f, -0.5f, 0.5f);

glVertex3f( 0.5f, -0.5f, -0.5f);

glVertex3f( 0.5f, 0.5f, -0.5f);

glVertex3f( 0.5f, 0.5f, 0.5f);

glColor3f(0.0f, 1.0f, 1.0f);

glVertex3f(-0.5f, -0.5f, 0.5f);

glVertex3f(-0.5f, -0.5f, -0.5f);

glVertex3f(-0.5f, 0.5f, -0.5f);

glVertex3f(-0.5f, 0.5f, 0.5f);

glColor3f(1.0f, 0.0f, 1.0f);

glVertex3f(-0.5f, 0.5f, 0.5f);

glVertex3f(-0.5f, 0.5f, -0.5f);

glVertex3f( 0.5f, 0.5f, -0.5f);

glVertex3f( 0.5f, 0.5f, 0.5f);

glColor3f(0.0f, 1.0f, 0.0f);

glVertex3f(-0.5f, -0.5f, 0.5f);

glVertex3f(-0.5f, -0.5f, -0.5f);

glVertex3f( 0.5f, -0.5f, -0.5f);

glVertex3f( 0.5f, -0.5f, 0.5f);

glEnd();

glPopMatrix();

Depth-Buffer

En 3D, tout n’est pas affiché : il y a des faces cachées. Comment déterminer ce qui est affiché ? En affichant ce qui est la plus proche de la caméra.

C’est la rôle du depth-buffer : il sauvegarde la distance du point actuellement affiché. Si un point plus proche arrive ensuite, il remplace le point et la valeur dans la depth-buffer est changée. Le depth-buffer à les même dimensions que le color-buffer.

Le depth-buffer doit être effacé avec glClear.

6. Paramètres du rendering

Antialiasing

L’OpenGL est une « state machine » : les paramètres de dessin consistent en une série de variables (très nombreuses, voir les annexes de la spécification OpenGL pour une liste complète).

La plupart sont modifiables par :

void glEnable( GLenum cap )

void glDisable( GLenum cap )

Pour l’antialiasing, les paramètres sont : GL_POINT_SMOOTH, GL_LINE_SMOOTH, et GL_POLYGON_SMOOTH. Le but de l’antialiasing est de rendre apparemment plus lisse les lignes en faisant un dégradé sur les contours des points.

Culling

Le culling consiste à ne dessiner que les triangles dont les points sont dans le sens des aiguilles d’une montre (« clockwise ») ou dans l’autre sens (« counter-clockwise »). L’intérêt est l’optimisation : l’objectif est de toujours dessiner les faces extérieures d’un objet dans le sens contraire des aiguilles d’une montre, et de filtrer les faces cachées avec le culling.

Prenons l’exemple du cube : les faces sont toujours affichées dans le même ordre, peut importe si elles sont visibles ou pas. C’est le depth-buffer qui permet d’afficher les bonnes faces. Cependant le test du depth-buffer est long : il faut calculer la distance de chaque point de chaque face par rapport à la caméra, puis tester le depth pour chaque point (et mettre à jour le depth si le point est plus près). C’est un test beaucoup trop important pour les faces dont on est sûr qu’elles ne seront pas affichées. Il est plus rapide d’éliminer une face entière d’un coup avec le culling.

L’astuce utilisée par le culling pour savoir de quel coté est vue une face consiste à regarder dans quel sens les points des triangles sont dessinés (en OpenGL tout est décomposé en triangles) : dans le sens des aiguilles d’une montre ou dans l’autre sens.

Cette technique est très rapide : en gros il suffit de regarder le signe du produit scalaire de deux cotés d’un triangle. La seule contrainte pour le programmeur est de dessiner dans le bon sens les points.

Le standard est le CCW (counter-clockwise), que ce soit pour les primitives de glu ou glut, ou encore pour 3DS ou Lightwave.

En OpenGL, les fonctions correspondantes sont :

void glCullFace( GLenum mode )

avec mode à GL_FRONT, GL_BACK ou GL_FRONT_AND_BACK, pour indiquer quel coté doit être éliminé au culling.

void glFrontFace( GLenum mode )

avec mode à GL_CW ou GL_CCW pour indiquer quelles sont les faces de devant

Un appel à glEnable / glDisable avec en paramètre GL_CULL_FACE pour activer / désactiver le culling.

7. Eclairage

Activation

C’est pour ce genre de choses que l’on apprécie la simplicité d’OpenGL, car activer l’éclairage se fait par :

glEnable(GL_LIGHTING) ; // active le calcul de l’éclairage

glEnable(GL_LIGHT0) ; // allume la lampe 0

C’est tout.

Pour allumer une autre lampe, mettre GL_LIGHTi au lieu de GL_LIGHT0. Il y a au minimum 8 lampes disponibles. On a toujours GL_LIGHTi = GL_LIGHT0 + i. Pour éteindre, utiliser glDisable.

Le nombre maximal de lumières est obtenu en appelant :

glGetIntegerv(GL_MAX_LIGHTS, &nb_lights)

Paramètres

Il faut quand même donner un minimum d’information sur la lampe, comme la couleur et la position de la lampe.

Un peu de théorie

Dans la réalité, les sources de lumières sont des volumes (par exemple un filament), et la lumière se réfléchit à l’infini sur tous les objets.

Il est impossible de simuler un tel comportement, surtout quand il faut calculer une image en quelques millisecondes. Des simplifications ont été apportées :

· les sources de lumière sont des points

· la lumière ne se réfléchit pas sur les objets

Pour plus de réalisme, les sources disposent de plusieurs paramètres qui n’ont pas forcément d’équivalent réel (voir le paragraphe suivant).

Une chose par contre ne change pas : la couleur visible d’un objet dépend à la fois de sa couleur propre et celle de la lampe, et l’intensité de la lumière qu’il reçoit dépend de l’angle de la surface avec la direction vers la source lumineuse.

Tout sur la lampe

Tiré tout droit de la spécification OpenGL :

Paramètre | Type | Par défaut | Description
acm | Couleur | (0,0,0,0) | Intensité ambiante : la lumière ambiante est présente partout dans la scène, elle n’a pas de direction
dcli | Couleur | (0,0,0,0) sauf lampe 0 : (1,1,1,1) | Intensité de diffusion : c’est l’intensité « réelle » de la source, elle part dans tous les sens depuis la source
scli | Couleur | Idem | Intensité du « spot » : comme la lumière diffuse, sauf qu’elle n’est émise qu’à l’intérieur d’un cône
Ppli | Position | (0,0,1,0) | Position
sdli | Direction | (0,0,-1) | Direction du spot (vecteur)
srli | Réel | 0 | Exposant du spot (entre 0 et 128)
crli | Réel | 180 | Angle du cône du spot (180 ou entre 0 et 90)
k0i | Réel | 1.0 | Atténuation constante (entre 0 et ¥)
k1i | Réel | 0 | Atténuation linéaire
k2i | Réel | 0 | Atténuation quadrique

Et sur les matériaux

Les mêmes paramètres existent pour spécifier la « réponse » de la surface d’un objet :

Paramètre | Type | Par défaut | Description
acm | Couleur | (0.2,0.2,0.2,1.0) | Couleur ambiante
dcm | Couleur | (0.8,0.8,0.8,1.0) | Couleur de diffusion
scm | Couleur | (0,0,0,1) | Couleur au « spot »
ecm | Couleur | (0,0,0,1) | Couleur émise
srm | Réel | 0 | Exposant au « spot »

Fonctions OpenGL

Les fonctions suivantes permettent de spécifier tous les paramètres précédents :

· Pour une source lumineuse :

void glLightf( Glenum light, Glenum pname, GLfloat param )

void glLighti( Glenum light, Glenum pname, GLint param )

light = GL_LIGHTi

pname = GL_SPOT_EXPONENT, GL_SPOT_CUTOFF, GL_CONSTANT_ATTENUATION, GL_LINEAR_ATTENUATION, ou GL_QUADRATIC_ATTENUATION

et param = la nouvelle valeur

void glLightfv( GLenum light, GLenum pname, const Glfloat *params)

void glLightiv( GLenum light, GLenum pname, const GLint *params)

light = GL_LIGHTi

pname = GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR, GL_POSITION, GL_SPOT_DIRECTION, GL_SPOT_EXPONENT, GL_SPOT_CUTOFF, GL_CONSTANT_ATTENUATION, GL_LINEAR_ATTENUATION, ou GL_QUADRATIC_ATTENUATION

param = la nouvelle valeur

Attention à la position de la lampe : comme toutes les coordonnées entrées, elles dont immédiatement multipliées par la matrice ModelView. Si celle-ci change, il faut repositionner les lampes.

void glLightModelf( GLenum pname, GLfloat param )

void glLightModeli( GLenum pname, GLint param )

void glLightModelfv( GLenum pname, const GLfloat *params )

void glLightModeliv( GLenum pname, const GLint *params )

Pname | param
GL_LIGHT_MODEL_AMBIENT (fv ou iv seulement) | Intensité ambiante de la scène, par défaut (0.2, 0.2, 0.2, 1.0)
GL_LIGHT_MODEL_LOCAL_VIEWER | 0 : les reflets des spots sont calculés sur l’axe –z ¹ 0 : par rapport à la position de l’œil
GL_LIGHT_MODEL_TWO_SIDE | 0 : seule la face « front » est calculée ¹ 0 : les deux faces d’une surface sont calculées

· Pour un matériau :

Les propriétés de surface d’un polygone peuvent être différentes suivant la face que la face est « front » ou « back » (voir glFrontFace, ou le paragraphe sur le culling).

void glMaterialfv( GLenum face, GLenum pname, const GLfloat *params )

void glMaterialiv( GLenum face, GLenum pname, const GLint *params )

La page man est tellement bien faite que je ne vois pas l’intérêt de la refaire :

Face | Specifies which face or faces are being updated. Must be one of GL_FRONT, GL_BACK, or GL_FRONT_AND_BACK.
Pname | Specifies the material parameter of the face or faces that is being updated. Must be one of GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR, GL_EMISSION, GL_SHININESS, GL_AMBIENT_AND_DIFFUSE, or GL_COLOR_INDEXES.
Params | Specifies a pointer to the value or values that pname will be set to.

glMaterial assigns values to material parameters. There are two matched sets of material parameters. One, the front-facing set, is used to shade points, lines, bitmaps, and all polygons (when two-sided lighting is disabled), or just front-facing polygons (when two-sided lighting is enabled). The other set, back-facing, is used to shade back-facing polygons only when two-sided lighting is enabled. Refer to the glLightModel reference page for details concerning one- and two-sided lighting calculations.

[…]

GL_AMBIENT | params contains four integer or floating-point values that specify the ambient RGBA reflectance of the material. Integer values are mapped linearly such that the most positive representable value maps to 1.0, and the most negative representable value maps to -1.0. Floating-point values are mapped directly. Neither integer nor floating-point values are clamped. The default ambient reflectance for both front- and back-facing materials is (0.2, 0.2, 0.2, 1.0).
GL_DIFFUSE | params contains four integer or floating-point values that specify the diffuse RGBA reflectance of the material. Integer values are mapped linearly such that the most positive representable value maps to 1.0, and the most negative representable value maps to -1.0. Floating-point values are mapped directly. Neither integer nor floating-point values are clamped. The default diffuse reflectance for both front- and back-facing materials is (0.8, 0.8, 0.8, 1.0).
GL_SPECULAR | params contains four integer or floating-point values that specify the specular RGBA reflectance of the material. Integer values are mapped linearly such that the most positive representable value maps to 1.0, and the most negative representable value maps to -1.0. Floating-point values are mapped directly. Neither integer nor floating-point values are clamped. The default specular reflectance for both front- and back-facing materials is (0.0, 0.0, 0.0, 1.0).
GL_EMISSION | params contains four integer or floating-point values that specify the RGBA emitted light intensity of the material. Integer values are mapped linearly such that the most positive representable value maps to 1.0, and the most negative representable value maps to -1.0. Floating-point values are mapped directly. Neither integer nor floating-point values are clamped. The default emission intensity for both front- and back-facing materials is (0.0, 0.0, 0.0, 1.0).
GL_SHININESS | params is a single integer or floating-point value that specifies the RGBA specular exponent of the material. Integer and floating-point values are mapped directly. Only values in the range [0,128] are accepted. The default specular exponent for both front- and back-facing materials is 0.
GL_AMBIENT_AND_DIFFUSE | Equivalent to calling glMaterial twice with the same parameter values, once with GL_AMBIENT and once with GL_DIFFUSE.

Les normales

Une fois que la source lumineuse est définie, ainsi que les caractéristiques du matériau, il reste encore une chose : l’angle d’incidence de la lumière. Cela ne change pas la couleur d’une surface, mais l’intensité de l’éclairage qu’elle reçoit.

L’angle d’incidence est l’angle entre la normale à une surface et la droite partant de la source lumineuse et passant par le point d’incidence. Il faut impérativement préciser pour chaque point la « normale » de ce point (plus rigoureusement, la normale à la surface dont ce point fait partie).

Le principe est le même que pour glColor, la normale est une des variables d’état d’OpenGL, qui est modifiée par l’une des fonctions suivantes :

void glNormal3b( GLbyte nx, GLbyte ny, GLbyte nz )

void glNormal3d( GLdouble nx, GLdouble ny, GLdouble nz )

void glNormal3f( GLfloat nx, GLfloat ny, GLfloat nz )

void glNormal3i( GLint nx, GLint ny, GLint nz )

void glNormal3s( GLshort nx, GLshort ny, GLshort nz )

void glNormal3bv( const GLbyte *v )

[...]

Les paramètres correspondent au coordonnées du vecteur normal. ATTENTION : il doit être UNITAIRE (de norme 1).

Il est possible de rendre unitaire automatiquement les vecteurs entrés, mais cela entraîne un calcul supplémentaire :

glEnable(GL_NORMALIZE);

Exemple

L’exemple le plus simple :

void init ()

{

glClearColor (0.0f, 0.0f, 0.0f, 0.0f);

GLfloat ambientProperties[] = {0.9f, 0.9f, 0.9f, 1.0f};

GLfloat positionProperties[] = {0.0f, -5.0f, 0.0f, 0.0f};

glLightfv( GL_LIGHT0, GL_DIFFUSE, ambientProperties);

glLightfv( GL_LIGHT0, GL_POSITION, positionProperties);

glEnable(GL_LIGHT0);

glEnable(GL_LIGHTING);

}

void display (void)

{

static timeb tt ,t;

static int total;

GLfloat Rouge[] = {0.8f, 0.0f, 0.0f, 1.0f};

GLfloat Vert[] = {0.0f, 0.8f, 0.0f, 1.0f};

GLfloat Bleu[] = {0.0f, 0.0f, 0.8f, 1.0f};

glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

ftime(&t);

total += (t.time - tt.time)*1000 + (t.millitm - tt.millitm);

total %= 360*300;

tt = t;

glPushMatrix();

glRotatef((total/30.0f), 0.5f, 0.3f, 0.8f);

glRotatef((total/50.0f), 0.5f, -0.3f, 0.8f);

glBegin(GL_QUADS);

glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, Rouge);

glNormal3d(0.0,0.0,1.0);

glVertex3d(-1, 1, 1);

glVertex3d(-1, -1, 1);

glVertex3d( 1, -1, 1);

glVertex3d( 1, 1, 1);

glNormal3d(0.0,0.0,-1.0);

glVertex3d( 1, 1, -1);

glVertex3d( 1, -1, -1);

glVertex3d( -1, -1, -1);

glVertex3d( -1, 1, -1);

glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, Vert);

glNormal3d(1.0,0.0,0.0);

glVertex3d( 1, 1, 1);

glVertex3d( 1, -1, 1);

glVertex3d( 1, -1, -1);

glVertex3d( 1, 1, -1);

glNormal3d(-1.0,0.0,0.0);

glVertex3d( -1, 1, 1);

glVertex3d( -1, 1, -1);

glVertex3d( -1, -1, -1);

glVertex3d( -1, -1, 1);

glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, Bleu);

glNormal3d(0.0,-1.0,0.0);

glVertex3d( -1, -1, 1);

glVertex3d( -1, -1, -1);

glVertex3d( 1, -1, -1);

glVertex3d( 1, -1, 1);

glNormal3d(0.0,1.0,0.0);

glVertex3d( -1, 1, 1);

glVertex3d( 1, 1, 1);

glVertex3d( 1, 1, -1);

glVertex3d( -1, 1, -1);

glEnd();

glPopMatrix();

glutSwapBuffers();

}

void reshape (int width, int height)

{

glViewport (0, 0, width, height);

glMatrixMode (GL_PROJECTION);

glLoadIdentity ();

gluPerspective (45.0, ((GLfloat) width) / ((GLfloat) height),1.0,10.0);

glMatrixMode (GL_MODELVIEW);

glLoadIdentity ();

gluLookAt (0.0, -5.0, 0.0,

0.0, 0.0, 0.0,

0.0, 0.0, 1.0);

init();

glEnable(GL_DEPTH_TEST);

glEnable(GL_POLYGON_SMOOTH);

glutPostRedisplay();

}

Pour voir l’effet des différents paramètres, voir les très bons exemples de www.cs.utah.edu/~narobins/tutors/tutor, et le lightlab de www.student.nada.kth.se/~nv91-gta/OpenGL/projects/lightlab/.

8. Fog (brouillard)

C’est ce qui simule les effets atmosphériques (je ne vous apprends rien là) comme la fumée, la pollution etc. On s’en sert aussi pour rendre les effets de vision limitée (en même temps, cela permet de virer de la scène une partie des objets 3D trop loin et d’accélérer le rendu).

Le fonctionnement du brouillard

Lorsque le brouillard est activé, les objets qui sont de plus en plus loin commencent à se fondre dans la couleur du brouillard. On peut contrôler aussi la densité du brouillard pour déterminer le rythme avec lequel les objets sont estompé lorsque la distance augmente.

Le brouillard est appliqué à la scène après les transformations matricielles, la lumière, et les textures, donc il affecte les objets transformés (ie avec texture…).

L’utilisation du brouillard

C’est très simple. On l’active en passant GL_FOG comme attribut de glEnable() et on la couleur et l’équation de contrôle de la densité du brouillard grâce à :

void glFogf( GLenum pname, GLfloat param )

void glFogi( GLenum pname, GLint param )

void glFogfv( GLenum pname, const GLfloat *params )

void glFogiv( GLenum pname, const GLint *params )

avec pname = GL_FOG_MODE, GL_FOG_DENSITY, GL_FOG_START, GL_FOG_END, ou GL_FOG_COLOR, et param la nouvelle valeur.

Exemple :

GlEnable(GL_FOG) ;

GLfloat fogcolor[4] = {0.5, 0.5, 0.5, 1} ;

GLint fogmode = GL_EXP ;

glFogi (GL_FOG_MODE, fogmode) ;

glFogfv(GL_FOG_COLOR, fogcolor) ;

glFogf(GL_FOG_DENSITY, 0.35) ;

glFogf(GL_FOG_START, 1.0) ;

glFogf(GL_FOG_END, 5.0) ;

Les équations

Le brouillard se sert d’une variable f variant de 0 (brouillard complet) à 1 (transparent comme de l’eau de roche). Les 3 équations sont :

· GL_LINEAR : f = (end – z) / (end – start)

· GL_EXP : f = e-density * z

· GL_EXP2 : f = e-(density * z)²

9. Transparence et alpha-blending

Plusieurs fois déjà des couleurs ont été présentée sous forme RGBA : rouge, vert, bleu et alpha. Si les trois premières coordonnées sont évidentes, la quatrième quant à elle sert à indiquer la « transparence ».

Activation

Si pour l’instant rien n’a été dit sur les valeurs de l’alpha, c’est simplement parce que par défaut OpenGL s’en fiche.

Pour activer le canal alpha, il faut appeler comme d’habitude glEnable :

glEnable(GL_BLEND) ;

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) ; // expliqué après

Ensuite il suffit de changer la valeur de A suivant la transparence.

Exemple

void init ()

{

glClearColor (0.0f, 0.0f, 0.0f, 0.0f);

glEnable(GL_BLEND) ;

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

}

void display (void)

{

static timeb tt ,t;

static int total;

GLfloat Rouge[] = {0.8f, 0.0f, 0.0f, 0.5f};

GLfloat Vert[] = {0.0f, 0.8f, 0.0f, 0.5f};

GLfloat Bleu[] = {0.0f, 0.0f, 0.8f, 0.5f};

// […]

glBegin(GL_QUADS);

glColor4fv(Rouge);

// […] (dessin cube)

glColor4fv(Vert);

// […]

glColor4fv(Bleu);

// […]

glEnd();

glPopMatrix();

glutSwapBuffers();

}

Paramètres

La paramétrage principal est effectué par la fonction :

void glBlendFunc( GLenum sfactor, GLenum dfactor )

Si la fonction s’appelle « blend » (« mélanger » en anglais, ici en parlant des couleurs) et pas « transparency », c’est que la transparence n’est qu’une application de l’alpha-blending. C’est de loin la principale.

Néanmoins ce que fait réellement l’alpha-blending, c’est mélanger la couleur déjà affichée avec une nouvelle. Et comme dans OpenGL, la rapidité du rendu est prioritaire sur la spécialisation des fonctions, il est bon de savoir comment cela fonctionne.

Le mélange est précisé par un appel à glBlendFunc, avec le premier paramètre indiquant par quelle valeur multiplier (canal par canal) la nouvelle couleur, le second la couleur déjà affichée. La nouvelle couleur affichée est la somme de ces deux multiplications.

L’indice « s » indique que la nouvelle couleur est concernée, « d » qu’il s’agit de la couleur déjà affichée.

La couleur affichée est : C = CsS + CdD. S et D étant paramétrés respectivement par sfactor et dfactor.

Principales valeurs de S en fonction de sfactor :

Valeur de sfactor | Valeur de S
ZERO | (0,0,0,0)
ONE | (1,1,1,1)
DST_COLOR | (Rd,Gd,Bd,Ad)
ONE_MINUS_DST_COLOR | (1,1,1,1) - (Rd,Gd,Bd,Ad)
SRC_ALPHA | (As, As, As, As)
ONE_MINUS_SRC_ALPHA | (1,1,1,1) - (As,As,As,As)
DST_ALPHA | (Ad,Ad,Ad,Ad)
ONE_MINUS_DST_ALPHA | (1,1,1,1) - (Ad,Ad,Ad,Ad)
SRC_ALPHA_SATURATE | (f,f,f,1) avec f = min (As, 1 - Ad)

Principales valeurs de D en fonction de dfactor :

Valeur de sfactor | Valeur de D
ZERO | (0,0,0,0)
ONE | (1,1,1,1)
SRC_COLOR | (Rs,Gs,Bs,As)
ONE_MINUS_SRC_COLOR | (1,1,1,1) - (Rs,Gs,Bs,As)
SRC_ALPHA | (As, As, As, As)
ONE_MINUS_SRC_ALPHA | (1,1,1,1) - (As,As,As,As)
DST_ALPHA | (Ad, Ad, Ad, Ad)
ONE_MINUS_DST_ALPHA | (1,1,1,1) - (Ad,Ad,Ad,Ad)
SRC_ALPHA_SATURATE | (f,f,f,1) avec f = min (As, 1 - Ad)

Les paramètres les plus courants sont (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA).

Cela représente bien la transparence codée dans le canal Alpha : la couleur affichée est multipliée par la valeur de transparence (si 0, complètement transparent, si 1 opaque), et la couleur en dessous est multipliée par le complément.

Ordre des primitives et depth-buffer

Le problème avec cette manière de rendre les objets transparents, c’est qu’il faut dessiner les surfaces dans l’ordre d’apparition pour avoir un meilleur rendu. En plus, si le depth-buffer est activé, les surfaces recouvertes sont directement éliminées au lieu d’être transparentes. Le plus simple, c’est de désactiver avec glDisable(GL_DEPTH_TEST) le depth-buffer lorsque des surfaces transparentes sont affichées.

10. Listes

Commandes OpenGL

Certains objets sont toujours les mêmes. Comme OpenGL traduit toutes les séquences entrées, il est intéressant de créer des listes pour regrouper les instructions. Cela accélère légèrement l’affichage et réduit le code. Une liste est alors codée par un entier (un seul entier est alors envoyé pour un objet quelconque).

En pratique :

· d’abord réserver des numéros de listes avec :
GLuint glGenLists( GLsizei range )
où range est le nombre de numéros de listes à réserver. Les numéros réservés vont de la valeur renvoyée à cette valeur + range.

· ensuite appeler :
void glNewList( GLuint list, GLenum mode )
avec pour list le numéro de la liste à créer, et mode = GL_COMPILE ou GL_COMPILE_AND_EXECUTE. Les deux modes créés la liste, mais le second en plus l’éxécute (l’affiche).

· mettre toutes les routines souhaitées (glBegin, glColor, glVertex…), elles sont enregistrées dans la liste. Certaines commandes ne peuvent pas être enregistrées dans une liste, mais il y en a peu et c’est plutôt logique (par exemple glGenList, glFlush, glNewlist…).

· terminer par :
void glEndList( void )

Pour afficher une liste, il faut appeler :

void glCallList( GLuint list )

ou

void glCallLists( GLsizei n, GLenum type, const GLvoid *lists)

pour afficher plusieurs listes d’un coup (type indique le type du tableau passé en paramètre : GL_BYTE, GL_INT…).

Reste à effacer une liste lorsqu’elle n’est plus utile :

void glDeleteLists( GLuint list, GLsizei range )

dont les paramètres sont les même que glGenList.

Exemple

Par exemple le cube peut être mis dans une liste :

void init()

{

GLfloat Rouge[] = {0.8f, 0.0f, 0.0f, 0.5f};

GLfloat Vert[] = {0.0f, 0.8f, 0.0f, 0.5f};

GLfloat Bleu[] = {0.0f, 0.0f, 0.8f, 0.5f};

cube = glGenLists(1);

glNewList(cube, GL_COMPILE);

glBegin(GL_QUADS);

glColor4fv(Rouge);

glNormal3d(0.0,0.0,1.0);

glVertex3d(-1, 1, 1);

glVertex3d(-1, -1, 1);

glVertex3d( 1, -1, 1);

glVertex3d( 1, 1, 1);

glNormal3d(0.0,0.0,-1.0);

glVertex3d( 1, 1, -1);

// […]

glEnd();

glEndList();

}

void display (void)

{

// […]

glPushMatrix();

glRotatef((total/30.0f), 0.5f, 0.3f, 0.8f);

glRotatef((total/50.0f), 0.5f, -0.3f, 0.8f);

glCallList(cube);

glPopMatrix();

glutSwapBuffers();

}

11. Textures

Jusqu’ici, les surfaces étaient simplement coloriées, ce qui ne permet pas d’avoir des rendus très réalistes. Les textures améliore considérablement le rendu. Le principe est de remplir une surface avec une image bitmap correspondant à une photo de la surface.

Principe général

Un texture est appliquée à une surface de la manière suivante :

· d’abord il faut indiquer quelle image il va falloir appliquer (ce qui revient à donner un pointeur vers l’image).

· ensuite, avant d’entrer chaque point (vertex) de la surface, il faut préciser où se situe ce point dans la texture, c’est le même principe qu’avant avec glColor. Cela revient en quelque sorte à mettre des épingles sur la texture

OpenGL se charge ensuite d’appliquer la texture, en faisant correspondre les vertex avec les « épingles ».

En OpenGL

Dans ce cours, les routines de chargement d’images en mémoire ne seront pas expliquées. Ce dont à besoin OpenGL, c’est d’une image non compressée chargée sous la forme d’un tableau en mémoire. Il existe de nombreuses routines toutes faites pour charger les principaux formats d’images (BMP, PCX, GIF, PNG…).

Spécifier l’image

Pour spécifier la texture à utiliser, appeler :

void glTexImage2D( GLenum target, GLint level, GLint components, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels )

Avec :

Target | GL_TEXTUTE_2D
Level | Indique le niveau de mipmapping, expliqué plus loin dans ce cours
Components | Nombre de composantes dans la texture (1, 2, 3 ou 4). Généralement 3 (pour RGB) ou 4 (pour RGBA).
Width | Largeur de l’image. Doit être 2n + 2 (dépend de bordure).
Height | Hauteur de l’image. Doit être 2m + 2 (dépend de bordure).
Bordure | Indique la largeur de la bordure : 0 ou 1
Format | Codage des couleurs : GL_COLOR_INDEX, GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_RGB, GL_RGBA, GL_LUMINANCE, ou GL_LUMINANCE_ALPHA
Type | Type du tableau : GL_UNSIGNED_BYTE, GL_BYTE, GL_BITMAP, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT, GL_INT, ou GL_FLOAT
Pixel | Pointeur vers l’image.

Les « épingles »

La première chose à faire, c’est d’activer les textures :

glEnable(GL_TEXTURE_2D) ;

Pour poser une épingle, avant un vertex il faut appeler :

void glTexCoord2f( GLfloat s, GLfloat t )

ou l’une de ses variantes.

Les coordonnées (s, t) indiquent la correspondance entre le prochain vertex entré et la texture. En OpenGL, le repère d’une texture fait toujours [0..1] sur [0..1], mais s et t peuvent sortir des ces intervalles, l’effet dépend alors du paramètrage choisi.

Paramétrage

Application de la texture

Les paramètres d’application d’une texture sont :

Nom | Type | Valeur possibles | Signification
GL_TEXTURE_WRAP_S | Entier | GL_CLAMP, GL_CLAMP_TO_EDGE, GL_REPEAT | Indique ce qui se passe si une coordonnée s sort de l’intervalle [0..1]. Soit la texture est répètée (mosaïque), soit seulement le bord est répété.
GL_TEXTURE_WRAP_T | Entier | Idem | Idem pout t
GL_TEXTURE_MIN_FILTER | Entier | NEAREST, LINEAR, NEAREST_MIPMAP_NEAREST, NEAREST_MIPMAP_LINEAR, LINEAR_MIPMAP_NEAREST, LINEAR_MIPMAP_LINEAR | Indique la qualité de l’agrandissement d’une texture. LINEAR est meilleur que NEAREST, mais plus lent.
GL_TEXTURE_MAG_FILTER | Entier | NEAREST, LINEAR | Idem en réduction

Il existe d’autres paramètres, mais ceux-ci sont de loin les plus importants.

Pour changer un paramètre, il faut appeler :

void glTexParameteri(GLenum target, GLenum pname, GLint param )

avec target = GL_TEXTURE_2D, pname l’un des noms de paramètres, et param la nouvelle valeur.

glColor

La couleur courante (spécifiée par glColor), joue un rôle différent suivant les paramètres passés à :

void glTexEnvi( GLenum target, GLenum pname, Glint param )

avec target = GL_TEXTURE_ENV, pname = GL_TEXTURE_ENV_MODE, et param = GL_REPLACE, GL_MODULATE, GL_DECAL, ou GL_BLEND.

Il y a beaucoup de cas suivant le nombre de composantes de la texture.

Dans le cas de 3 ou 4 composantes (RGB ou RGBA), les modes les plus courants sont :

· GL_REPLACE, la couleur courante n’intervient pas

· GL_MODULATE, la couleur courante multiplie la texture. Il est alors possible de changer très facilement l’intensité d’une texture.

Exemple

Pour simplifier (et garantir la portabilité), le format d’image est ici le « Raw », c’est-à-dire l’image seule. Paint Shop Pro gère très bien ce format. Le problème est que la taille de l’image n’est pas inclus dans ce fichier.

void init()

{

char buffer[32*32*3];

FILE *f = fopen("../Data/Marbre.raw", "rb");

if (f)

{

fread(buffer, 3072, 1, f);

fclose(f);

glTexImage2D(GL_TEXTURE_2D,0,3, 32, 32, 0, GL_RGB, GL_UNSIGNED_BYTE, buffer);

glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

glEnable(GL_TEXTURE_2D);

}

cube = glGenLists(1);

glNewList(cube, GL_COMPILE);

glBegin(GL_QUADS);

glNormal3d(0.0,0.0,1.0);

glTexCoord2f(0.0, 1.0); glVertex3d(-1, 1, 1);

glTexCoord2f(1.0, 1.0); glVertex3d(-1, -1, 1);

glTexCoord2f(1.0, 0.0); glVertex3d( 1, -1, 1);

glTexCoord2f(0.0, 0.0); glVertex3d( 1, 1, 1);

glNormal3d(0.0,0.0,-1.0);

glTexCoord2f(0.0, 1.0); glVertex3d( 1, 1, -1);

glTexCoord2f(0.0, 0.0); glVertex3d( 1, -1, -1);

glTexCoord2f(1.0, 0.0); glVertex3d( -1, -1, -1);

glTexCoord2f(1.0, 1.0); glVertex3d( -1, 1, -1);

glNormal3d(1.0,0.0,0.0);

glTexCoord2f(0.0, 0.0); glVertex3d( 1, 1, 1);

glTexCoord2f(1.0, 0.0); glVertex3d( 1, -1, 1);

glTexCoord2f(1.0, 1.0); glVertex3d( 1, -1, -1);

glTexCoord2f(0.0, 1.0); glVertex3d( 1, 1, -1);

glNormal3d(-1.0,0.0,0.0);

glTexCoord2f(0.0, 0.0); glVertex3d( -1, 1, 1);

glTexCoord2f(1.0, 0.0); glVertex3d( -1, 1, -1);

glTexCoord2f(1.0, 1.0); glVertex3d( -1, -1, -1);

glTexCoord2f(0.0, 1.0); glVertex3d( -1, -1, 1);

glNormal3d(0.0,-1.0,0.0);

glTexCoord2f(0.0, 0.0); glVertex3d( -1, -1, 1);

glTexCoord2f(1.0, 0.0); glVertex3d( -1, -1, -1);

glTexCoord2f(1.0, 1.0); glVertex3d( 1, -1, -1);

glTexCoord2f(0.0, 1.0); glVertex3d( 1, -1, 1);

glNormal3d(0.0,1.0,0.0);

glTexCoord2f(0.0, 0.0); glVertex3d( -1, 1, 1);

glTexCoord2f(1.0, 0.0); glVertex3d( 1, 1, 1);

glTexCoord2f(1.0, 1.0); glVertex3d( 1, 1, -1);

glTexCoord2f(0.0, 1.0); glVertex3d( -1, 1, -1);

glEnd();

glEndList();

}

MipMapping

Le MipMapping consiste à avoir la même texture à plusieurs échelles, de façon à limiter les transferts mémoires lors du plaquage de texture (inutile de transférer une texture 256x256 si l’objet dessiné est très loin dans le décor et ne fait plus que du 5x5).

En théorie, le programmeur devrait avoir 3 ou 4 fois la même texture, mais avec une résolution différente. Il faut alors entrer toutes les textures à chaque fois, en changeant le paramètre level de glTextImage2D (sachant que 0 est l’image la plus grande, et les suivantes sont ses réductions). Lorsque la texture est appliqué, OpenGL soit choisi l’image de taille la plus proche de la surface à afficher (NEAREST_MIPMAP), soit fait une sorte de moyenne entre l’image de taille juste au dessus et celle juste en dessous (LINEAR_MIPMAP).

En pratique, il y a une fonction glu :

int gluBuild2DMipmaps(GLenum target, GLint components, GLint width, GLint height, GLenum format, GLenum type, const void *data )

Les paramètres sont les mêmes que pour glTexImage2D, sauf qu’il est cette fois possible d’utiliser des images de dimensions qui ne sont pas des puissances de 2. L’image passée est celle de meilleure qualité (level = 0).

Chargement dans la carte vidéo

L’intérêt du MipMapping est très limité si les textures ne sont pas stockées dans la carte vidéo.

Le principe est similaire à celui des listes :

void glGenTextures( GLsizei n, GLuint * textures)

permet de réserver n textures, les numéros sont alors placés dans le tableau pointé par textures.

Ensuite le numéro de la texture courante est changé par :

void glBindTexture( GLenum target, GLuint texture)

avec target = GL_TEXTURE_2D et texture le numéro de la texture à activer. Ensuite toutes les fonctions faisant intervenir une texture utilisent la texture active.

Les textures sont libérées par :

void glDeleteTextures(GLsizei n, const GLuint * textures )

Pour résumé, une texture est le plus souvent chargée par :

glGenTextures(1, &Texture_num);

glBindTexture(GL_TEXTURE_2D, Texture_num);

gluBuild2DMipmaps(GL_TEXTURE_2D, 3, width, height(), GL_RGB, GL_UNSIGNED_BYTE, data);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);

Ensuite pour activer une texture, avant un glBegin :

glBindTexture(GL_TEXTURE_2D, Texture_num);

Exemple

void init()

{

char buffer[32*32*3];

FILE *f = fopen("../Data/Marbre.raw", "rb");

if (f)

{

fread(buffer, 3072, 1, f);

fclose(f);

glGenTextures(1, &Texture1);

glBindTexture(GL_TEXTURE_2D, Texture1);

glTexImage2D(GL_TEXTURE_2D,0,3, 32, 32, 0, GL_RGB, GL_UNSIGNED_BYTE, buffer);

glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

glEnable(GL_TEXTURE_2D);

}

f = fopen("../Data/Marbre2.raw", "rb");

if (f)

{

fread(buffer, 3072, 1, f);

fclose(f);

glGenTextures(1, &Texture2);

glBindTexture(GL_TEXTURE_2D, Texture2);

glTexImage2D(GL_TEXTURE_2D,0,3, 32, 32, 0, GL_RGB, GL_UNSIGNED_BYTE, buffer);

}

cube = glGenLists(1);

glNewList(cube, GL_COMPILE);

glBindTexture(GL_TEXTURE_2D, Texture2);

glBegin(GL_QUADS);

glNormal3d(0.0,0.0,1.0);

glTexCoord2f(0.0, 1.0); glVertex3d(-1, 1, 1);

glTexCoord2f(1.0, 1.0); glVertex3d(-1, -1, 1);

glTexCoord2f(1.0, 0.0); glVertex3d( 1, -1, 1);

glTexCoord2f(0.0, 0.0); glVertex3d( 1, 1, 1);

glNormal3d(0.0,0.0,-1.0);

glTexCoord2f(1.0, 0.0); glVertex3d( 1, 1, -1);

glTexCoord2f(1.0, 1.0); glVertex3d( 1, -1, -1);

glTexCoord2f(0.0, 1.0); glVertex3d( -1, -1, -1);

glTexCoord2f(0.0, 0.0); glVertex3d( -1, 1, -1);

glNormal3d(1.0,0.0,0.0);

glTexCoord2f(1.0, 1.0); glVertex3d( 1, 1, 1);

glTexCoord2f(0.0, 1.0); glVertex3d( 1, -1, 1);

glTexCoord2f(0.0, 0.0); glVertex3d( 1, -1, -1);

glTexCoord2f(1.0, 0.0); glVertex3d( 1, 1, -1);

glNormal3d(-1.0,0.0,0.0);

glTexCoord2f(0.0, 0.0); glVertex3d( -1, 1, 1);

glTexCoord2f(1.0, 0.0); glVertex3d( -1, 1, -1);

glTexCoord2f(1.0, 1.0); glVertex3d( -1, -1, -1);

glTexCoord2f(0.0, 1.0); glVertex3d( -1, -1, 1);

glEnd();

glBindTexture(GL_TEXTURE_2D, Texture1);

glBegin(GL_QUADS);

glNormal3d(0.0,-1.0,0.0);

glTexCoord2f(0.0, 0.0); glVertex3d( -1, -1, 1);

glTexCoord2f(1.0, 0.0); glVertex3d( -1, -1, -1);

glTexCoord2f(1.0, 1.0); glVertex3d( 1, -1, -1);

glTexCoord2f(0.0, 1.0); glVertex3d( 1, -1, 1);

glNormal3d(0.0,1.0,0.0);

glTexCoord2f(0.0, 0.0); glVertex3d( -1, 1, 1);

glTexCoord2f(1.0, 0.0); glVertex3d( 1, 1, 1);

glTexCoord2f(1.0, 1.0); glVertex3d( 1, 1, -1);

glTexCoord2f(0.0, 1.0); glVertex3d( -1, 1, -1);

glEnd();

glEndList();

}

Priorités des textures

Comme il se peut que toutes les textures ne rentrent pas dans la carte vidéo, il est possible de donner des priorités au chargement des textures en mémoire vidéo.

void glPrioritizeTextures( GLsizei n, GLuint * textures, GLclampf * priorities)

avec priorities un tableau de n flottants appartenant à [0..1], 0 étant la priorité la plus faible et 1 la plus forte.

12. Les opérations sur les pixels

Introduction

Tout ce qu’on va voir ici concerne l’affichage de bitmaps, la copie et l’affichage de blocs de pixels et la façon dont les pixels sont stockés en mémoire.

L’utilisation la plus courante en OpenGL du transfert de pixels est l’ajout d’images qui recouvrent la scène (photo d’un cockpit, nom du joueur, score…). Il est plus facile des les afficher en utilisant les coordonnées écrans (par exemple 800x600), il faut alors repasser en mode 2D (ce qui ne fait que changer la matrice de projection, tout ce qui est 3D ne bouge pas) :

glMatrixMode(GL_PROJECTION) ;

glLoadIdentity() ;

gluOrtho2D(0.0, (Glfloat) width, 0.0, (Glfloat) height) ;

glMatrixMode(GL_MODEL_VIEW) ;

glLoadIdentity() ;

La Raster Position

La position actuelle de rastérisation est l’origine du prochain bitmap affiché. Le bitmap est affiché au dessus et à droite de cette position. Exemple :

glRasterPos2i(20, 20) ;

La façon de trouver la position est la même que celle de glVertex dans le cas ou l’environnement est en 3D. Si la position est hors du clipping (zone d’affichage), la position est dite invalide. Pour obtenir la Raster Position actuelle et sa validité , on utilise :

GLfloat vector[4] ;

glGetFloatv(GL_CURRENT_RASTER_POSITION, vector) ;

glGetBooleanv(GL_CURRENT_RASTER_POSITION_VALID) ;

Dessiner le bitmap

Après avoir choisi la Raster Position, on peut dessiner le bitmap grâce à l’instruction glBitmap dont le prototype est :

void glBitmap (GLsizei width, GLsizei height, GLfloat Xbo Ybo Xbi Ybi, const GLubyte *bitmap) ;

Si la Raster Position est invalide, rien n’est affiché. On utilise Xbo et Ybo pour définir l’origine du bitmap par rapport à la Raster Position. Xbi et Ybi sont les incréments à la Raster Position après avoir affiché le bitmap. Pour choisir la couleur d’affichage du bitmap, on utilise glColor juste avant.

Le gros problème de cette fonction est qu’elle ne gère pas le format RGB ou RGBA (seulement le mode 8 bits). Les fonctions des paragraphes suivants sont un peu plus lourdes à manipuler, mais plus générales.

Lire, écrire et copier des images

Pour lire une image, la fonction glReadPixels doit être utilisée :

void glReadPixels (Glint x, y, Glsizei width, height, Glenum format, type, Glvoid *pixels) ;

x et y représentent le coin bas-gauche de l’image à lire. Width et height sont ses dimensions. Le format et le type servent à définir le type de structure qui sera utilisé pour le stockage. Les différents types de format sont :

Format

GL_COLOR_INDEX

GL_RGB Dans l’ordre : Rouge, Vert, Bleu

GL_RGBA RGB et après Alpha

GL_RED

GL_GREEN

GL_BLUE

GL_ALPHA

GL_LUMINANCE

GL_LUMINANCE_ALPHA luminance suivi de alpha

GL_STENCIL_INDEX

GL_DEPTH_COMPONENT

Type

GL_UNSIGNED_BYTE (8bits)

GL_BYTE (8 bits)

GL_BITMAP (même format que pour glBitmap)

GL_UNSIGNED_SHORT (16 bits)

GL_SHORT (16 bits)

GL_UNSIGNED_INT (32 bits)

GL_INT (32 bits)

GL_FLOAT (flottant en simple précision)

! Il faut bien faire attention de faire correspondre le type de *pixel avec celui indiqué par le format et le type.

Pour écrire une image, on utilise la fonction glDrawPixel :

void glDrawPixels(Glsizei widht, height, Glenum format, type, const Glvoid *pixels) ;

Cela dessine un rectangle contenant l’image pointée par *pixels à la Raster Position et de la taille widht * height. Les valeurs de format et de type sont les mêmes que celles de glReadPixels.

La fonction glCopyPixels permet de copier un rectangle d’un buffer spécifié par le cinquième paramètre (GL_COLOR, GL_STENCIL ou GL_DEPTH) vers la raster position.

void glCopyPixels(Glint x, y, Glsizei width, height, Glenum buffer) ;

Agrandissement, réduction et réflexion

Normalement, chaque pixel d’une image est écrit dans un pixel sur l’écran. La fonction

void glPixelZoom(Glfloat, zoomx, zoomy) ;

permet de modifier la taille que chaque pixel prend sur l’écran. Si on donne 2 pour chaque argument, chaque pixel est représenté à l’écran par 4 pixels. Des valeurs non entières et négatives sont permises. Les facteurs négatifs de zoom font une réflexion par rapport à la raster position.

13. Tests de validation et stencil buffer

Chaîne de tests

Il est tant de voir la liste complète des tests de validation d’un point :

à Scissor Test à Alpha Test à Stencil Test à Depth Test à […] à framebuffer

Scissor test

Le scissor test est paramétré par :

void glScissor( GLint x, GLint y, GLsizei width, GLsizei height )

Cela permet de limiter l’affichage à un rectangle dont le coin haut – gauche à pour coordonnées (x, y) dans la fenêtre. Par défaut (x, y) = (0, 0) et width et height ont la même taille que la fenêtre.

Le test est activé par : glEnable(GL_SCISSOR_TEST)

Alpha test

Un point peut être éliminé par sa valeur alpha avec :

void glAlphaFunc( GLenum func, GLclampf ref )

avec func = GL_NEVER, GL_LESS, GL_EQUAL, GL_LEQUAL, GL_GREATER, GL_NOTEQUAL, GL_GEQUAL, ou GL_ALWAYS

Ne pas oublier, comme toujours : glEnable(GL_ALPHA_TEST)

Stencil test

Ce test est plus complexe, mais plus utile que les deux précédents.

Tout comme le depth-buffer, le stencil-buffer est un espace mémoire de la taille du framebuffer. Mais cette fois son utilisation est libre.

Sous glut, ne pas oublier de le rajouter à l’initialisation :

glutInitDisplayMode (GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH | GLUT_STENCIL)

Effacement

D’abord, le stencil étant comme le depth un buffer, il faut (le plus souvent) l’effacer à chaque image avec :

glClear(… | GL_STENCIL_BUFFER_BIT) ;

La valeur de remplissage est paramétrable avec :

void glClearStencil( GLint s )

Initialisation

Comme pour tous les tests, il faut appeler :

glEnable(GL_STENCIL_TEST) ;

Le test est paramétré par deux fonctions :

void glStencilFunc( GLenum func, GLint ref, GLuint mask)

qui sert à indiquer le test à effectuer, les paramètres sont :

· func = GL_NEVER, GL_LESS, GL_LEQUAL, GL_GREATER, GL_GEQUAL, GL_EQUAL, GL_NOTEQUAL, ou GL_ALWAYS

· ref = valeur de référence du test

· mask = masque binaire à appliquer à la valeur du stencil et à ref pour le test

void glStencilOp( GLenum fail, GLenum zfail, GLenum zpass )

qui sert à indiquer ce qui se passe suivant le résultat du test du stencil et du depth, avec :

· fail indique l’opération à effectuer sur la valeur dans le stencil lorsque le stencil test a échoué. Peut être égal à GL_KEEP, GL_ZERO, GL_REPLACE, GL_INCR, GL_DECR, ou GL_INVERT

· zfail indique de même l’opération à effectuer si le stencil est passé mais pas le depth

· zpass indique l’opération à effectuer si le stencil et le depth est passent

14. Ombres et reflets

La principale application du stencil buffer, ce sont les ombres et les reflets.

Reflets

Principe

Les reflets sur un plan sont très simples : ce n’est qu’un double d’un objet, recouvert d’une surface légèrement transparente pour donner l’illusion qu’il s’agit d’un reflet.

En plus, ce qui simplifie bien les choses, il existe la fonction :

void glScalef( GLfloat x, GLfloat y, GLfloat z )

qui multiplie la matrice courante par une matrice multipliant toutes les coordonnées entrées par les valeurs spécifiées, c’est-à-dire une matrice diagonale (x, y, z, 1).

Dans notre cas, pour effectuer une symétrie par rapport au plan y = 0, il suffit d’entrer glScalef(1.0f, -1.0f, 1.0f).

Seul problème : le reflet peut sortir de la surface miroir ! C’est là que le stencil buffer intervient. La surface réfléchissante est d’abord « virtuellement » dessinée, en fait elle est dessinée normalement sauf que pour empêcher l’affichage on utilise :

void glColorMask( GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha )

avec tous les paramètres à GL_FALSE. Cela permet de changer la valeur du stencil là où la surface va être dessinée.

Les étapes du reflets sont finalement :

· Limitation du reflet à la surface réfléchissante :
glDisable(GL_DEPTH_TEST);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
glEnable(GL_STENCIL_TEST);
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
glStencilFunc(GL_ALWAYS, 1, 0xffffffff);
Dessin de la surface réfléchissante
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glEnable(GL_DEPTH_TEST);

· Dessin du reflet :
glStencilFunc(GL_EQUAL, 1, 0xffffffff); /* draw if ==1 */
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glPushMatrix();
glScalef(1.0, -1.0, 1.0);
respositionnement des lumières
dessin de l’objet reflété
glPopMatrix();
respositionnement des lumières
glDisable(GL_STENCIL_TEST);

· Dessin de la surface réfléchissante :
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Dessin de la surface réfléchissante
glDisable(GL_BLEND);

· Dessin de l’objet

Exemple

Le programme est cette fois complet (même le main y est).

#include <GL/glut.h>

#include <sys/timeb.h>

#include <math.h>

GLint cube = 0;

void idle (void)

{

glutPostRedisplay ();

}

void drawFloor()

{

glColor4f(0.5f, 0.5f, 0.5f, 0.75f);

glBegin(GL_QUADS);

glVertex3f(-4.0, 4.0, 0.0);

glVertex3f( 4.0, 4.0, 0.0);

glVertex3f( 4.0, -4.0, 0.0);

glVertex3f(-4.0, -4.0, 0.0);

glEnd();

}

void SetLight()

{

GLfloat ambientProperties[] = {0.7f, 0.7f, 0.7f, 1.0f};

GLfloat diffuseProperties[] = {1.0f, 1.0f, 1.0f, 1.0f};

GLfloat positionProperties[] = {5.0f, 0.0f, 0.0f, 0.0f};

glLightfv( GL_LIGHT0, GL_AMBIENT, ambientProperties);

glLightfv( GL_LIGHT0, GL_DIFFUSE, diffuseProperties);

glLightfv( GL_LIGHT0, GL_POSITION, positionProperties);

glEnable(GL_LIGHT0);

glEnable(GL_LIGHTING);

}

void display (void)

{

static timeb tt ,t;

static int total;

GLfloat Rouge[] = {0.8f, 0.0f, 0.0f, 1.0f};

GLfloat Vert[] = {0.0f, 0.8f, 0.0f, 1.0f};

GLfloat Bleu[] = {0.0f, 0.0f, 0.8f, 1.0f};

glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

ftime(&t);

total += (t.time - tt.time)*1000 + (t.millitm - tt.millitm);

total %= 360*300;

tt = t;

glDisable(GL_DEPTH_TEST);

glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);

glEnable(GL_STENCIL_TEST);

glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);

glStencilFunc(GL_ALWAYS, 1, 0xffffffff);

drawFloor();

glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);

glEnable(GL_DEPTH_TEST);

glStencilFunc(GL_EQUAL, 1, 0xffffffff); /* draw if ==1 */

glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

glPushMatrix();

glScalef(1.0, 1.0, -1.0);

SetLight();

glTranslatef(sin(total/30.0f/180.0f*3.1415f)*5.0f, 0.0f, 5.0f);

glRotatef((total/30.0f), 0.5f, 0.3f, 0.8f);

glRotatef((total/50.0f), 0.5f, -0.3f, 0.8f);

SetLight();

glCallList(cube);

glPopMatrix();

SetLight();

glDisable(GL_STENCIL_TEST);

glEnable(GL_BLEND);

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

glDisable(GL_LIGHTING);

drawFloor();

glEnable(GL_LIGHTING);

glDisable(GL_BLEND);

glPushMatrix();

glTranslatef(sin(total/30.0f/180.0f*3.1415f)*5.0f, 0.0f, 5.0f);

glRotatef((total/30.0f), 0.5f, 0.3f, 0.8f);

glRotatef((total/50.0f), 0.5f, -0.3f, 0.8f);

glCallList(cube);

glPopMatrix();

glutSwapBuffers();

}

void init ()

{

GLfloat Rouge[] = {0.8f, 0.0f, 0.0f, 1.0f};

GLfloat Vert[] = {0.0f, 0.8f, 0.0f, 1.0f};

GLfloat Bleu[] = {0.0f, 0.0f, 0.8f, 1.0f};

cube = glGenLists(1);

glNewList(cube, GL_COMPILE);

glBegin(GL_QUADS);

glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, Rouge);

glNormal3d(0.0,0.0,1.0);

glVertex3d(-1, 1, 1);

glVertex3d(-1, -1, 1);

glVertex3d( 1, -1, 1);

glVertex3d( 1, 1, 1);

glNormal3d(0.0,0.0,-1.0);

glVertex3d( 1, 1, -1);

glVertex3d( 1, -1, -1);

glVertex3d( -1, -1, -1);

glVertex3d( -1, 1, -1);

glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, Vert);

glNormal3d(1.0,0.0,0.0);

glVertex3d( 1, 1, 1);

glVertex3d( 1, -1, 1);

glVertex3d( 1, -1, -1);

glVertex3d( 1, 1, -1);

glNormal3d(-1.0,0.0,0.0);

glVertex3d( -1, 1, 1);

glVertex3d( -1, 1, -1);

glVertex3d( -1, -1, -1);

glVertex3d( -1, -1, 1);

glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, Bleu);

glNormal3d(0.0,-1.0,0.0);

glVertex3d( -1, -1, 1);

glVertex3d( -1, -1, -1);

glVertex3d( 1, -1, -1);

glVertex3d( 1, -1, 1);

glNormal3d(0.0,1.0,0.0);

glVertex3d( -1, 1, 1);

glVertex3d( 1, 1, 1);

glVertex3d( 1, 1, -1);

glVertex3d( -1, 1, -1);

glEnd();

glEndList();

}

void reshape (int width, int height)

{

glViewport (0, 0, width, height);

glMatrixMode (GL_PROJECTION);

glLoadIdentity ();

gluPerspective (45.0, ((GLfloat) width) / ((GLfloat) height),1.0,100.0);

glMatrixMode (GL_MODELVIEW);

glLoadIdentity ();

gluLookAt (0.0,-7.0,15.0, 0.0, 0.0, 0.0,0.0,0.0, 1.0);

glEnable(GL_DEPTH_TEST);

glEnable(GL_POLYGON_SMOOTH);

SetLight();

glutPostRedisplay();

}

int main (int argc, char *argv[])

{

glutInit (&argc, argv);

glutInitDisplayMode (GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH | GLUT_STENCIL);

glutInitWindowSize (640, 480);

glutCreateWindow ("Cours09.c");

glutDisplayFunc (display);

glutReshapeFunc (reshape);

glutIdleFunc (idle);

init();

glutMainLoop ();

return 0;

}

Matrice « miroir »

L’utilisation de glScale est beaucoup moins pratique dès que la surface n’est pas un plan parallèle à un axe. Heureusment, M.J. Kilgard (de SGI / NVidia) à mis en ligne la fonction :

Ombres

Principe

Le stencil buffer joue toujours son rôle de délimitation, pour l’ombre cette fois. Reste à dessiner l’ombre elle-même, or qu’est-ce qu’une ombre ? C’est la projection de tout un objet sur un plan suivant la direction de la lumière. Cela fait (devrait faire ?) immédiatement penser à « matrice de projection ». Effectivement il suffit de multiplier la matrice ModelView par une matrice de projection pour avoir le résultat voulu.

Quelques détails tout de même :

· l’ombre n’est pas le dessin de l’objet lui-même mais une surface noire (à moitié transparente). Bien que dans la réalité, ce n’est pas exactement cela : c’est tout le reste de la scène qui est plus éclairée. Cependant il est bien plus rapide d’assombrir là où est l’ombre que d’éclaircir tout le reste. S’il n’y a aucune lumière ambiante, l’ombre n’est pas transparente.

· avec cette méthode, il faut faire attention à ne pas assombrir deux fois le même point : ce n’est pas parce que deux surfaces cachent la lumière que l’ombre est deux fois plus sombre ! L’astuce consiste ici à incrémenter le stencil lorsque le stencil test et le depth test passent (dès qu’un point est assombri, il ne fait plus « partie » de la surface).
Cependant, il existe un cas où il faut volontairement oublier ce fait : lorsque l’objet projeté est en partie transparent. Il est alors possible d’avoir un assombrissement proportionnel au nombre de surfaces qui cachent la lumière. Sauf qu’il ne faut pas trop en abuser, le temps de calcul s’allongeant encore.

· le problème de la matrice de projection : elle n’est pas dans glu ! Alors remercions une fois encore M.J. Kilgard pour sa documentation en ligne :

/* Create a matrix that will project the desired shadow. */

enum { X = 0, Y, Z, W };

void shadowMatrix(GLfloat shadowMat[4][4], GLfloat groundplane[4], GLfloat lightpos[4])

{

GLfloat dot;

dot = groundplane[X] * lightpos[X] +

groundplane[Y] * lightpos[Y] +

groundplane[Z] * lightpos[Z] +

groundplane[W] * lightpos[W];

shadowMat[0][0] = dot - lightpos[X] * groundplane[X];

shadowMat[1][0] = 0.f - lightpos[X] * groundplane[Y];

shadowMat[2][0] = 0.f - lightpos[X] * groundplane[Z];

shadowMat[3][0] = 0.f - lightpos[X] * groundplane[W];

shadowMat[X][1] = 0.f - lightpos[Y] * groundplane[X];

shadowMat[1][1] = dot - lightpos[Y] * groundplane[Y];

shadowMat[2][1] = 0.f - lightpos[Y] * groundplane[Z];

shadowMat[3][1] = 0.f - lightpos[Y] * groundplane[W];

shadowMat[X][2] = 0.f - lightpos[Z] * groundplane[X];

shadowMat[1][2] = 0.f - lightpos[Z] * groundplane[Y];

shadowMat[2][2] = dot - lightpos[Z] * groundplane[Z];

shadowMat[3][2] = 0.f - lightpos[Z] * groundplane[W];

shadowMat[X][3] = 0.f - lightpos[W] * groundplane[X];

shadowMat[1][3] = 0.f - lightpos[W] * groundplane[Y];

shadowMat[2][3] = 0.f - lightpos[W] * groundplane[Z];

shadowMat[3][3] = dot - lightpos[W] * groundplane[W];

}

avec shadowMat la matrice à construire, groundpane l’équation du plan où projeter l’ombre (de type ax + by + cz + d = 0), et lightpos comme son nom l’indique (lightpos[3] de préférence à 0).

Et pour les coefficients du plan :

/* Find the plane equation given 3 points. */

enum { A = 0, B, C, D };

void findPlane(GLfloat plane[4], GLfloat v0[3], GLfloat v1[3], GLfloat v2[3])

{

GLfloat vec0[3], vec1[3];

/* Need 2 vectors to find cross product. */

vec0[X] = v1[X] - v0[X];

vec0[Y] = v1[Y] - v0[Y];

vec0[Z] = v1[Z] - v0[Z];

vec1[X] = v2[X] - v0[X];

vec1[Y] = v2[Y] - v0[Y];

vec1[Z] = v2[Z] - v0[Z];

/* find cross product to get A, B, and C of plane equation */

plane[A] = vec0[Y] * vec1[Z] - vec0[Z] * vec1[Y];

plane[B] = -(vec0[X] * vec1[Z] - vec0[Z] * vec1[X]);

plane[C] = vec0[X] * vec1[Y] - vec0[Y] * vec1[X];

plane[D] = -(plane[A] * v0[X] + plane[B] * v0[Y] + plane[C] * v0[Z]);

}

Les étapes de l’ombre sont alors :

· Limitation de l’ombre à la surface :
glEnable(GL_STENCIL_TEST);
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
glStencilFunc(GL_ALWAYS, 1, 0xffffffff);
Dessin de la surface
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glEnable(GL_DEPTH_TEST);
glDisable(GL_STENCIL_TEST);

· Dessin de l’objet

· Calcul de la matrice de projection (avec vfloor des points de la surface) :
GLfloat floorPlane[4];
GLfloat floorShadow[4][4];
findPlane(floorPlane, vfloor[1], vfloor[2], vfloor[3]);
shadowMatrix(floorShadow, floorPlane, lightZeroPosition);

·
Changement des paramètres pour dessiner une
surface noire transparente :
glStencilFunc(GL_EQUAL, 1, 0xffffffff);
/* draw if ==1 */
glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA,
GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_LIGHTING);
glDisable(GL_TEXTURE_2D);
glDisable(GL_DEPTH_TEST);
glColor4f(0.0, 0.0, 0.0, 0.5);

· Dessin de l’ombre :
glPushMatrix();
glMultMatrixf((GLfloat *) floorShadow);
dessin de l’objet
glPopMatrix();

Exemple

GLfloat vfloor[4][3] = {-4, 4, 0, 4, 4, 0, 4,-4, 0,-4,-4, 0};

GLfloat positionProperties[] = {0.0f, -3.0f, 5.0f, 0.0f};

void drawFloor()

{

glColor4f(0.5f, 0.5f, 0.5f, 0.75f);

glBegin(GL_QUADS);

glVertex3fv(vfloor[0]);

glVertex3fv(vfloor[1]);

glVertex3fv(vfloor[2]);

glVertex3fv(vfloor[3]);

glEnd();

}

void SetLight()

{

GLfloat ambientProperties[] = {0.7f, 0.7f, 0.7f, 1.0f};

GLfloat diffuseProperties[] = {1.0f, 1.0f, 1.0f, 1.0f};

glLightfv( GL_LIGHT0, GL_AMBIENT, ambientProperties);

glLightfv( GL_LIGHT0, GL_DIFFUSE, diffuseProperties);

glLightfv( GL_LIGHT0, GL_POSITION, positionProperties);

glEnable(GL_LIGHT0);

glEnable(GL_LIGHTING);

}

void display (void)

{

static timeb tt ,t;

static int total;

GLfloat Rouge[] = {0.8f, 0.0f, 0.0f, 1.0f};

GLfloat Vert[] = {0.0f, 0.8f, 0.0f, 1.0f};

GLfloat Bleu[] = {0.0f, 0.0f, 0.8f, 1.0f};

glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

ftime(&t);

total += (t.time - tt.time)*1000 + (t.millitm - tt.millitm);

total %= 360*300;

tt = t;

glDisable(GL_DEPTH_TEST);

glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);

glEnable(GL_STENCIL_TEST);

glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);

glStencilFunc(GL_ALWAYS, 1, 0xffffffff);

drawFloor();

glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);

glEnable(GL_DEPTH_TEST);

glDisable(GL_STENCIL_TEST);

glDisable(GL_LIGHTING);

drawFloor();

glEnable(GL_LIGHTING);

glEnable(GL_STENCIL_TEST);

glStencilFunc(GL_ALWAYS, 2, 0xffffffff);

glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);

glPushMatrix();

glTranslatef(sin(total/30.0f/180.0f*3.1415f)*5.0f, 0.0f, 3.0f);

glRotatef((total/30.0f), 0.5f, 0.3f, 0.8f);

glRotatef((total/50.0f), 0.5f, -0.3f, 0.8f);

glCallList(cube);

glPopMatrix();

glStencilFunc(GL_EQUAL, 1, 0xffffffff); /* draw if ==1 */

glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);

glEnable(GL_BLEND);

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

glDisable(GL_LIGHTING);

glDisable(GL_DEPTH_TEST);

glColor4f(0.0, 0.0, 0.0, 0.5);

static GLfloat floorPlane[4];

static GLfloat floorShadow[4][4];

findPlane(floorPlane, vfloor[1], vfloor[2], vfloor[3]);

shadowMatrix(floorShadow, floorPlane, positionProperties);

glPushMatrix();

glMultMatrixf((GLfloat *) floorShadow);

glTranslatef(sin(total/30.0f/180.0f*3.1415f)*5.0f, 0.0f, 3.0f);

glRotatef((total/30.0f), 0.5f, 0.3f, 0.8f);

glRotatef((total/50.0f), 0.5f, -0.3f, 0.8f);

glCallList(cube);

glPopMatrix();

glEnable(GL_DEPTH_TEST);

glDisable(GL_BLEND);

glEnable(GL_LIGHTING);

glDisable(GL_STENCIL_TEST);

glFlush();

glutSwapBuffers();

}

15. Courbes et Surfaces calculées

Présentation

Pour certaines surfaces, comme par exemple les sphères, il se pose un problème : elles ne peuvent être qu’approchées par des triangles. Il faut faire un compromis : soit avoir une sphère très belle, ce qui peut prendre plusieurs milliers de triangles, ou bien avoir peu de triangles mais une sphère pas très sphérique. De plus, pourquoi avoir des surfaces précises si elles sont éloignées ? De toute façon elles ne font que quelques pixels.

D’où l’intérêt d’avoir des surfaces calculées : la précision de la surface est ajustée suivant les besoins (distance par rapport à l’œil), et même peut être ajustée dynamiquement suivant la puissance de la machine (si le programme tourne à 60 fps, une amélioration visuelle est raisonnable).

Quadrics

Présentation

Glu intègre déjà un certain nombre de formes : sphère, cylindre et disque. Le principe est toujours le même :

· Déclaration d’un « quadric » :
GLUquadricObj obj = gluNewQuadric();

· Appel(s) à l’une des fonctions suivantes (dessin immédiat) :
void gluCylinder( GLUquadricObj *qobj, GLdouble baseRadius, GLdouble topRadius, GLdouble height, GLint slices, GLint stacks )
void gluDisk( GLUquadricObj *qobj, GLdouble innerRadius, GLdouble outerRadius, GLint slices, GLint loops )
void gluPartialDisk( GLUquadricObj *qobj, GLdouble innerRadius, GLdouble outerRadius, GLint slices, GLint loops, GLdouble startAngle, GLdouble sweepAngle )
void gluSphere( GLUquadricObj *qobj, GLdouble radius, GLint slices, GLint stacks )

· Destruction avec :
void gluDeleteQuadric( GLUquadricObj *state )

Suivant les paramètres du rendu, il est possible de générer :

· Les normales (pour l’éclairage) :
void gluQuadricNormals( GLUquadricObj *qobj, GLenum normals )
avec normals = GLU_NONE, GLU_FLAT, ou GLU_SMOOTH

· Les coordonnées de texture :
void gluQuadricTexture( GLUquadricObj *qobj, GLboolean textureCoords )

· Le mode de dessin :
void gluQuadricDrawStyle( GLUquadricObj *qobj, GLenum drawStyle )
avec drawStyle = GLU_FILL, GLU_LINE, GLU_SILHOUETTE, ou GLU_POINT

Exemple

GLfloat positionProperties[] = {5.0f, -5.0f, 5.0f, 0.0f};

void SetLight()

{

GLfloat ambientProperties[] = {0.7f, 0.7f, 0.7f, 1.0f};

GLfloat diffuseProperties[] = {1.0f, 1.0f, 1.0f, 1.0f};

glLightfv( GL_LIGHT0, GL_AMBIENT, ambientProperties);

glLightfv( GL_LIGHT0, GL_DIFFUSE, diffuseProperties);

glLightfv( GL_LIGHT0, GL_POSITION, positionProperties);

glEnable(GL_LIGHT0);

glEnable(GL_LIGHTING);

glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, ambientProperties);

}

void display (void)

{

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

GLUquadricObj *obj = gluNewQuadric();

gluQuadricNormals(obj, GL_SMOOTH);

gluQuadricDrawStyle(obj, GLU_FILL);

gluSphere(obj, 1.0, 20, 20);

gluDeleteQuadric(obj);

glFlush();

glutSwapBuffers();

}

Bézier

OpenGL intègre (ce n’est pas glu cette fois) les courbes et les surfaces de Bézier. Cependant les cartes grand public n’accélèrent pas leur calcul. Pour l’instant…

Le principe d’une courbe de Bézier est le suivant :

- Soit n point de contrôle, la courbe part du premier et arrive au dernier

- La courbe est calculée par récurrence, en rajoutant entre deux points de contrôle leur milieu jusqu’à la résolution de l’affichage. Seul les milieux du dernier niveau de récurrence sont affichés (en fait dès qu’un point, hormis les extrémités la courbe, a servi pour calculer un milieu, il est éliminé). Un petit dessin pour essayer de mieux comprendre :

Le principe est le même pour les surfaces.

Courbe

Fonctions OpenGL

La première chose à faire est de rentrer les points de contrôle :

void glMap1f( GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points )

Target

Indique quel type de coordonnées il faut générer. Les choix sont : GL_MAP1_VERTEX_3, GL_MAP1_VERTEX_4, GL_MAP1_INDEX, GL_MAP1_COLOR_4, GL_MAP1_NORMAL, GL_MAP1_TEXTURE_COORD_1, GL_MAP1_TEXTURE_COORD_2, GL_MAP1_TEXTURE_COORD_3, ou GL_MAP1_TEXTURE_COORD_4

Pour une courbe de Bézier, il faut choisir GL_MAP1_VERTEX_3. Mais il est aussi possible d’utiliser GL_MAP1_COLOR_4 pour créer un dégradé de couleurs : quelle différence entre générer des coordonnées (x, y,z) et (R, G, B) ?

u1, u2

Une courbe est ensuite repérée par un « axe courbe ». Par la suite, la courbe débutera en (u1) et se terminera en (u2)

Stride

Indique le nombre de flottants qui sépare un point de contrôle dans points du suivant. Le seul but de cette valeur est de pouvoir utiliser des tableaux de structure avec d’autres données que les points.

Order

Nombre de points dans le tableau

Points

Pointeur vers les points de contrôle

Il faut appeler glEnable avec le même target pour activer la courbe.

Ensuite la fonction :

void glEvalCoord1f( GLfloat u)

fait l’équivalent d’un glVertex(coordonnées du point repéré par u). Par exemple glEvalCoord1f(u1) à le même effet que glVertex(premier point de contrôle), et de même glEvalCoord1f(u2) à le même effet que glVertex(dernier point de contrôle). La valeur de u n’est pas limitée à [u1..u2]. Si elle sort de cette intervalle, cela prolonge simplement la courbe.

La fonction doit être appelée à l’interieur d’un glBegin / glEnd.

Exemple

void display (void)

{

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glPushMatrix();

static GLfloat ctrlpoints[4][3] = {

{-4.0, -4.0, -15.0}, {-2.0, 4.0, -15.0},

{ 2.0, -4.0, -15.0}, { 4.0, 4.0, -15.0}};

glColor3f(1.0f,1.0f,1.0f);

glMap1f(GL_MAP1_VERTEX_3, 0.0, 1.0, 3, 4, &ctrlpoints[0][0]);

glEnable(GL_MAP1_VERTEX_3);

glBegin(GL_LINE_STRIP);

for (GLfloat i = 0.0; i <= 1.0; i+=0.05f)

glEvalCoord1f(i);

glEnd();

glDisable(GL_MAP1_VERTEX_3);

glPointSize(5.0);

glColor3f(1.0, 1.0, 0.0);

glBegin(GL_POINTS);

for (int j = 0; j < 4; j++)

glVertex3fv(&ctrlpoints[j][0]);

glEnd();

glPopMatrix();

glFlush();

glutSwapBuffers();

}

void reshape (int width, int height)

{

glViewport (0, 0, width, height);

glMatrixMode (GL_PROJECTION);

glLoadIdentity ();

gluPerspective (45.0, ((GLfloat) width) / ((GLfloat) height),1.0,100.0);

glMatrixMode (GL_MODELVIEW);

glLoadIdentity ();

gluLookAt(0, 0, 0, 0, 0, -1, 0, 1, 0);

glEnable(GL_DEPTH_TEST);

glEnable(GL_POLYGON_SMOOTH);

}

Surface

Fonctions OpenGL

Les fonctions sont les mêmes avec des « 2 » au lieu de « 1 », et le tableau des points de contrôle est à 2 dimensions :

void glMap2f( GLenum target, Glfloat u1, Glfloat u2, GLint ustride, GLint uorder, Glfloat v1, Glfloat v2, GLint vstride, GLint vorder, const GLfloat *points )

void glEvalCoord2f( GLfloat u, GLfloat v)

Il y cependant une fonction supplémentaire bien utile :

void glEvalMesh2( GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2 )

qui est purement et simplement :

glBegin(mode);

for (j = j1; j <= j2; j += 1)

for (i = i1; i <= i2; i += 1)

glEvalCoord2(i·du + u1, j·dv + v1);

glEnd();

avec du = (u2 – u1) / i et dv = (v2 – v1) / j

(mode est au choix : GL_QUAD_STRIP, GL_LINE_STRIP, GL_POINTS).

Exemple

void display (void)

{

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glPushMatrix();

static GLfloat ctrlpoints[4][4][3] = {

{{-1.5, -1.5, 4.0}, {-0.5, -1.5, 2.0},

{ 0.5, -1.5, -1.0}, { 1.5, -1.5, 2.0}},

{{-1.5, -0.5, 1.0}, {-0.5, -0.5, 3.0},

{ 0.5, -0.5, 0.0}, { 1.5, -0.5, -1.0}},

{{-1.5, 0.5, 4.0}, {-0.5, 0.5, 0.0},

{ 0.5, 0.5, 3.0}, { 1.5, 0.5, 4.0}},

{{-1.5, 1.5, -2.0}, {-0.5, 1.5, -2.0},

{ 0.5, 1.5, 0.0}, { 1.5, 1.5, -1.0}}

};

glEnable(GL_LINE_SMOOTH);

glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 12, 4, &ctrlpoints[0][0][0]);

glEnable(GL_MAP2_VERTEX_3);

static GLfloat tt= 0;

if ((tt += 0.05f) > 360) tt -= 360.0f;

glTranslatef(0.0f, 0.0f, -10.0f);

glRotatef(tt, 0.7f, 0.0f, 1.0f);

glColor3f(1.0, 1.0, 1.0);

glMapGrid2f(9, 0.0, 1.0, 9, 0.0, 1.0);

glEvalMesh2(GL_LINE, 0, 9, 0, 9);

glDisable(GL_MAP2_VERTEX_3);

glPopMatrix();

glFlush();

glutSwapBuffers();

}

void reshape (int width, int height)

{

glViewport (0, 0, width, height);

glMatrixMode (GL_PROJECTION);

glLoadIdentity ();

gluPerspective (45.0, ((GLfloat) width) / ((GLfloat) height),1.0,100.0);

glMatrixMode (GL_MODELVIEW);

glLoadIdentity ();

gluLookAt(0, 0, 0,

0, 0, -0.5f,

0, 1, 0);

glEnable(GL_DEPTH_TEST);

glEnable(GL_POLYGON_SMOOTH);

}

16. Ce qui n’a pas de place ailleurs

Dessiner deux fois au même endroit

Il se peut qu’il faille dessiner deux fois au même endroit, pour mettre un logo sur un rectangle par exemple. Le depth-buffer pose alors un problème : il n’est pas précis, ou en tout cas pas suffisament précis pour être sûr qu’ancun point du logo ne va être éliminé.

La solution la plus simple : désactiver le depth-test. Mais ce n’est à utiliser que lorsque l’on est sûr qu’il n’y a rien de plus proche de la caméra.

Une autre solution, parfaitement sûre mais plus compliquée, c’est d’utiliser le stencil buffer : dessiner d’abord le logo et changer la valeur du stencil là où le logo est dessiné. Dessiner ensuite le rectangle sur lequel est placé le logo, mais seulement là où le stencil n’a pas bougé.

Dernière solution, la fonction :

void glPolygonOffset(Glfloat factor, Glfloat units)

avec factor qui permet de « lisser » la profondeur (limite la dérivée du depth, sans intérêt ici), et units le nombre « d’unités » de décalage à ajouter au point. L’unité est dépendante de l’implémentation GL, et est spécifiée comme étant « le plus petit décalage tel que deux points ayant la même profonfeur soit quand même disctincts ». Dans le cas qui nous intéresse, cela donne :

glPolygonOffset(0, 1);

Il faut appeler glEnable avec GL_POLYGON_OFFSET_FILL pour activer le décalage des points.

Cette méthode fonctionne assez bien, mais la précision n’atteint pas celle du stencil : si le nombre de calculs (nombre de matrcies dans modelview) est trop différent pour le logo et la surface, il se peut très bien (pour cause de calcul en flottant et d’imprécision du depth) que cette technique ne marche pas. Elle est par contre très fiable si les matrices utilisées sont les mêmes pour le logo et la surface.

Clipping

Il est possible de rajouter des plans de clipping, pour exclure certaines parties du monde dessiné. Il en existe toujours au moins deux : ceux qui limitent l’intervalle de z (automatiquement générés lors de l’appel de gluPerspective).

Un plan quelconque peut être ajouté avec :

void glClipPlane( GLenum plane, const GLdouble *equation )

plane étant GL_CLIP_PLANEi (=GL_CLIP_PLANE0 + i), la valeur max étant GL_MAX_CLIP_PLANES. Equation est un tableau de quatre flottants donnant l’équation du plan (ax + by + cz + d = 0). Le nombre minimal de plans possible est 6 (pour définir un cube).

Pour l’activer : glEnable(GL_CLIP_PLANEi).

La règle pour savoir si un vertex est coupé ou pas est : « If the dot product of the eye coordinates of a vertex with the stored plane equation components is positive or zero, the vertex is in with respect to that clipping plane. Otherwise, it is out. »

Multitexturing

Le but de cette partie est de montrer les possibilités d’OpenGL en combinant plusieurs des fonctions vues jusqu’à présent.

Le « multitexturing » consiste à faire plusieurs rendus d’une même surface en changeant certaines options, pour obtenir des effets lumineux. L’exemple donné ici consiste à dessiner deux fois le même objet, une fois avec une texture non éclairée, puis une deuxième fois pour ajouter un rendu sans texture mais éclairé.

En OpenGL, cela donne à peu près :

glEnable(GL_BLEND);

glDepthFunc(GL_LEQUAL);

glDepthMask(GL_TRUE);

glDisable(GL_LIGHTING);

glDisable(GL_DITHER);

glShadeModel(GL_FLAT);

glEnable(GL_TEXTURE_2D);

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

Draw();

glEnable(GL_LIGHTING);

glDisable(GL_TEXTURE_2D);

glDepthFunc(GL_EQUAL);

glDepthMask(GL_FALSE);

glEnable(GL_DITHER);

glBlendFunc(GL_ONE, GL_ONE);

Draw();

²

Vu dans ... Le guide du routard dans OpenGL 1

The END


C:\D_zOrigine\COM_CHM\CHM Prgm LIBRARY\___THE LIBRARY OpenGL\OpGL 2\__Collect\__BIN\Opengl GLUT ZIP\OpenGL_cours\TOTO.rtf

Le guide du routard dans OpenGL 1.2
Orion (F. Gasser)
Gritche (J-F. Herouard)

Sommaire

TOC \o "1-2" \h \z 1. Introduction PAGEREF _Toc468199406 \h 4
1.1. Qu’est ce que OpenGL PAGEREF _Toc468199407 \h 4
1.2. Les bonnes adresses PAGEREF _Toc468199408 \h 5
1.3. Les bons documents PAGEREF _Toc468199409 \h 5
2. GLUT, l’OpenGL Utility Toolkit PAGEREF _Toc468199410 \h 5
2.1. Un exemple PAGEREF _Toc468199411 \h 6
2.2. L’initialisation PAGEREF _Toc468199412 \h 6
2.3. Les callbacks PAGEREF _Toc468199413 \h 7
2.4. Travail en background PAGEREF _Toc468199414 \h 7
2.5. Les fonctions de dessin 3D évoluées PAGEREF _Toc468199415 \h 7
2.6. Exécuter le programme PAGEREF _Toc468199416 \h 8
3. Initialisation de la vue PAGEREF _Toc468199417 \h 8
3.1. Exemple d’initialisation 2D PAGEREF _Toc468199418 \h 8
3.2. Présentation des matrices d’OpenGL PAGEREF _Toc468199419 \h 8
4. Dessiner PAGEREF _Toc468199420 \h 9
4.1. Un classique : le triangle PAGEREF _Toc468199421 \h 9
4.2. Les Buffers PAGEREF _Toc468199422 \h 9
4.3. Bouger le triangle, utilisation de la matrice de vue PAGEREF _Toc468199423 \h 12
5. Dessiner en 3D PAGEREF _Toc468199424 \h 13
5.1. Initialisation PAGEREF _Toc468199425 \h 13
5.2. Exemple : un Cube PAGEREF _Toc468199426 \h 14
5.3. Depth-Buffer PAGEREF _Toc468199427 \h 15
6. Paramètres du rendering PAGEREF _Toc468199428 \h 15
6.1. Antialiasing PAGEREF _Toc468199429 \h 15
6.2. Culling PAGEREF _Toc468199430 \h 15
7. Eclairage PAGEREF _Toc468199431 \h 16
7.1. Activation PAGEREF _Toc468199432 \h 16
7.2. Paramètres PAGEREF _Toc468199433 \h 17
7.3. Fonctions OpenGL PAGEREF _Toc468199434 \h 18
7.4. Les normales PAGEREF _Toc468199435 \h 20
7.5. Exemple PAGEREF _Toc468199436 \h 21
8. Fog (brouillard) PAGEREF _Toc468199437 \h 22
8.1. Le fonctionnement du brouillard PAGEREF _Toc468199438 \h 22
8.2. L’utilisation du brouillard PAGEREF _Toc468199439 \h 22
8.3. Les équations PAGEREF _Toc468199440 \h 22
9. Transparence et alpha-blending PAGEREF _Toc468199441 \h 23
9.1. Activation PAGEREF _Toc468199442 \h 23
9.2. Exemple PAGEREF _Toc468199443 \h 23
9.3. Paramètres PAGEREF _Toc468199444 \h 23
9.4. Ordre des primitives et depth-buffer PAGEREF _Toc468199445 \h 24
10. Listes PAGEREF _Toc468199446 \h 25
10.1. Commandes OpenGL PAGEREF _Toc468199447 \h 25
10.2. Exemple PAGEREF _Toc468199448 \h 25
11. Textures PAGEREF _Toc468199449 \h 26
11.1. Principe général PAGEREF _Toc468199450 \h 26
11.2. En OpenGL PAGEREF _Toc468199451 \h 26
11.3. Exemple PAGEREF _Toc468199452 \h 28
11.4. MipMapping PAGEREF _Toc468199453 \h 29
11.5. Chargement dans la carte vidéo PAGEREF _Toc468199454 \h 29
11.6. Exemple PAGEREF _Toc468199455 \h 30
11.7. Priorités des textures PAGEREF _Toc468199456 \h 30
12. Les opérations sur les pixels PAGEREF _Toc468199457 \h 31
12.1. Introduction PAGEREF _Toc468199458 \h 31
12.2. La Raster Position PAGEREF _Toc468199459 \h 31
12.3. Dessiner le bitmap PAGEREF _Toc468199460 \h 31
12.4. Lire, écrire et copier des images PAGEREF _Toc468199461 \h 32
12.5. Agrandissement, réduction et réflexion PAGEREF _Toc468199462 \h 33
13. Tests de validation et stencil buffer PAGEREF _Toc468199463 \h 33
13.1. Chaîne de tests PAGEREF _Toc468199464 \h 33
13.2. Scissor test PAGEREF _Toc468199465 \h 33
13.3. Alpha test PAGEREF _Toc468199466 \h 33
13.4. Stencil test PAGEREF _Toc468199467 \h 33
14. Ombres et reflets PAGEREF _Toc468199468 \h 35
14.1. Reflets PAGEREF _Toc468199469 \h 35
14.2. Ombres PAGEREF _Toc468199470 \h 38
15. Courbes et Surfaces calculées PAGEREF _Toc468199471 \h 41
15.1. Présentation PAGEREF _Toc468199472 \h 41
15.2. Quadrics PAGEREF _Toc468199473 \h 41
15.3. Bézier PAGEREF _Toc468199474 \h 42
16. Ce qui n’a pas de place ailleurs PAGEREF _Toc468199475 \h 45
16.1. Dessiner deux fois au même endroit PAGEREF _Toc468199476 \h 45
16.2. Clipping PAGEREF _Toc468199477 \h 45
16.3. Multitexturing PAGEREF _Toc468199478 \h 46

Introduction

Qu’est ce que OpenGL

OpenGL est une interface de programmation vers le matériel graphique. Cette interface compte environ 150 commandes qui sont utilisées dans les programmes interactifs en 3D. Etant donné que cette API est à l’origine professionnelle, il y a beaucoup de fonctions qui ne sont toujours pas câblées sur les cartes accélératrices 3D pour le grand public, et qui seront donc émulées en software.

La première particularité d’OpenGL est que la programmation est indépendante du matériel graphique utilisé, et que son domaine d’utilisation n’est pas confiné aux PC/Windows ou Mac. A l’origine développé par Silicon Graphics, OpenGL est aujourd’hui disponible sur les plates-formes x86 et 68000 entre autres, que ce soit sous Windows, Linux ou Mac. La conséquence immédiate est qu’aucune commande de fenêtrage, ou d’une manière générale toute commande dépendant de l’OS, n’est incluse et que seuls des commandes graphiques de bas niveaux existent (dessiner un point, un segment mais pas de sphère par exemple). Tout objet complexe est composé à partir d’un nombre limité de primitives graphiques.

Deuxièmement, OpenGL se base toujours sur d’autres librairies graphiques qui sont en général spécifiques au système d’exploitation ou qui apportent des fonctions complexes par dessus la librairie OpenGL. L’OpenGL Utility Library (GLU) ainsi que l’OpenGL Utility Toolkit (GLUT) en sont des exemples.

Une autre particularité est que certaines implémentations d’OpenGL sont conçues pour que la station sur laquelle on affiche ne soit pas celle sur laquelle tourne le programme. Cette fonctionnalité n’est utilisé que sur les systèmes basés sur l’X, et ne sera donc pas détaillée ici (en gros, cela est possible car OpenGL utilise une architecture client (un programme) - serveur (la carte vidéo et son driver OpenGL)).

Silicon Graphics n’a pas implémenté de version pour Linux de OpenGL. Il convient donc d’utiliser Mesa, qui est une librairie compatible OpenGL (attention, la compatibilité n’est ni garantie ni totale, .... Gritche tire une sale gueule la parce qu’il est sûr que Mesa 3.1 est compatible OpenGL 1.2, il dit aussi que ça s’appelle Mesa parce que l’emploi du nom OpenGL™ n’est pas possible en GNU).

Les bonnes adresses

Voici quelques bonnes adresse à visiter pour avoir accès à des informations complémentaires et aux manuels de référence :

OpenGL :
http://www.opengl.org : news, documentation, liens…
http://trant.sgi.com/opengl : des exemples, des astuces
http://reality.sgi.com/opengl : idem
GLU :
http://www.opengl.org/Documentation/GLX.html
GLUT :
http://reality.sgi.com/mjk/spec3/spec3.html
Mesa :
http://www.mesa3d.org
AGL (Extension OpenGL pour Apple) :
http://www.sd.tgs.com/Products/opengl.htm
PGL (Extension OpenGL pour IBM OS/2 Warp
http://www.austin.ibm.com/software/OpenGL/

Sur la machine de Game, il y a déjà dans les 250 Mo de download provenant de ces sites, alors avant de dans lancer un wget sauvage à l’école, vérifiez :-). En plus les exemples sont déjà compilés.
Si vous trouvez quelque chose d’intéressant qui n’est pas sur la machine, n’hésitez pas à en faire part à tout le monde.

Les bons documents

Il y a quelques documents de références en ligne qui sont bons à avoir : opengl1.2.pdf, glu1.3.pdf, glut.spec.ps et les man pages d’OpenGL en Html (une page html par fonction).

GLUT, l’OpenGL Utility Toolkit

OpenGL ne s’occupant pas des opérations relatives au système ou au fenêtrage, il laisse la place à d’autres API, dont GLUT fait parti. Il existe des implémentations de GLUT pour Windows NT, X-Window et OS/2, ce qui permet de compiler le même code source sur toutes les plates-formes.

GLUT est une interface entre le programme et l’humanoïde qui se trouve derrière l’écran, la souris, ou le joystick. Sa programmation est de type événementielle et chaque agitation de l’humain est considéré comme un événement qui sera géré par des fonctions personnalisées (callback).

Un exemple

Voici un premier exemple de programme utilisant OpenGL :

#include <GL/glut.h>

int main(int argc, char** argv)
{
Glint Xscreen = 800;
Glint Yscreen = 600;

glutInit(&argc, argv);
glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize (Xscreen, Yscreen);
glutCreateWindow (argv[0]);

glutDisplayFunc (display);
glutReshapeFunc (reshape);
glutIgnoreKeyRepeat(1);
glutKeyboardFunc(keyboard);
glutKeyboardUpFunc(keyboardup);
glutSpecialFunc(special);
glutSpecialUpFunc(specialup) ;
glutMouseFunc(mouse);
glutMotionFunc(motion);
glutPassiveMotionFunc(motion);
glutIdleFunc (IntelligenceArtificielle);
glutMainLoop ();
return(0);
}

L’initialisation

On remarque que l’on fait plusieurs appels à des fonctions GLUT pour créer une fenêtre. Dans l’ordre, on a :

glutInit(int *argc, char **argv) : A être appelé avant tout autre fonction GLUT. Cette fonction gere sous X les paramètres tels que -display et -geometry.
glutInitDisplayMode(unsigned int mode) : différencie les mode RGBA et couleur indexé, ainsi que le double-buffering et les buffers à utiliser (depth, stencil, accumulation), que nous étudierons par la suite
glutInitWindowPostion(int x, int y) : no comment
glutInitWindowSize(int width, int size) : no comment
int glutCreatWindow(char *string) : crée la fenêtre (et un contexte OpenGL) avec tous les paramètres spécifiés avant. Elle renvoie un identifiant unique pour la nouvelle fenêtre. Attention : la fenêtre ne sera pas affichée avant l’appel à glutMainLoop()

Les callbacks

Viennent ensuite les « callback ». Il s’agit de spécifier quels sont les fonctions à appeler suivant les événements qui se produisent. On peut changer les fonctions appelées à n’importe quel moment. Si on spécifie la valeur NULL comme adresse de fonction à utiliser, cela a pour effet de désactiver le callback correspondant. Elles sont par défaut à NULL. Les plus utilisés des callback sont :

glutDisplayFunc(void(*func)(void)) : indique la fonction à utiliser lors de l’affichage graphique.
glutReshapeFunc(void(*func)(int w, int h)) : si la fenêtre est redimentionnée
glutKeyboardFunc(void(*func)(unsigned char key, int x, int y)) et glutMouseFunc(void(*func)(int button, int state, int x, int y)) : événement clavier et souris
glutMotionFunc(void (*func)(int x, int y))

Travail en background

Lorsque le programme n’a plus rien a faire, ie l’affichage a été traité, et les callback aussi, on peut spécifier de rentrer dans un fonction qui peut se charger par exemple de faire évoluer le monde virtuel. On utilise alors la fonction :

GlutIdleFunc(void (*func)(void)) : comme d’hab, NULL passé en paramètre désactive cette fonction.

Les fonctions de dessin 3D évoluées

GLUT peut se charger de dessiner des objets 3D centrés sur l’origine en mode fil de fer (wire) ou solide (solid). Les objets sont :

cone icosahedron teapot

cube octahedron tetrahedron

dodecahedron sphere torus

Pour dessiner un cube ou une sphère, on utilise par exemple les fonctions :

void glutWireCube(Gldouble size) ;
void glutSolidCube(Gldouble size) ;
void glutWireSphere(Gldouble radius, Glint slices, Glint stacks) ;
void glutSolidSphere(Gldouble radius, Glint slices, Glint stacks) ;

Exécuter le programme

Pour exécuter le programme et voir réellement quelque chose s’afficher après l’initialisation, il faut appeler la fonction :

glutMainLoop(void) Ici, on rentre dans la boucle infinie d’exécution. Ca doit être à peut de choses prêt la dernière ligne du programme. On ne pourra sortir de cette boucle que par un exit().

Initialisation de la vue
Exemple d’initialisation 2D

OpenGL est plus connu pour son utilisation 3D, mais il est aussi prévu pour fonctionner en 2D. Comme c’est un peu plus facile à appréhender, on commence par là.

Une fois que le « contexte » OpenGL est crée, il reste à créer la vue. Cela se fait ainsi :

GLsizei width = cx, height = cy; // width, height : taille de la fenêtre
GLdouble aspect;

if (cy == 0)
aspect = (GLdouble)width;
else
aspect = (GLdouble)width/(GLdouble)height;
glMatrixMode(GL_PROJECTION); // initialisation de la matrice de projection
glLoadIdentity();
gluOrtho2D(-1.0f * aspect, 1.0f * aspect, -1.0f, 1.0f);
glMatrixMode(GL_MODELVIEW); // initialisation de la matrice de vue
glLoadIdentity();

Il s’agit ici d’une initialisation d’une vue 2D.

Présentation des matrices d’OpenGL

En OpenGL, toutes les transformations géométriques sont entrées sous forme de matrice, la vue est définie par la position de la caméra (matrice de changement de repère) et par la matrice de projection.
La matrice de projection s’appelle GL_PROJECTION. Il n’est possible d’accéder qu’à une matrice à la fois, donc pour modifier une matrice, il faut d’abord l’activer avec glMatrixMode. La matrice choisie devient la « matrice courante ». Il reste à définir la matrice de projection, mais inutile de se revoir le cours de prépa, car heureusement des fonctions de glu permettent de spécifier les matrices les plus courantes. La fonction
void gluOrtho2D( GLdouble left, GLdouble right, GLdouble bottom, GLdouble top )
permet dans l’exemple de mettre une matrice de projection 2D de la dimension voulue.
GlLoadIdentity initialise la matrice à l’identité.

La matrice GL_MODELVIEW est en quelque sorte la matrice de positionnement de la vue. C’est le plus souvent une matrice de changement de repère. Il faut la considérer comme une matrice qui positionne les objets dans le repère du monde, et éviter de la considérer comme la position de la caméra, même si c’est aussi son rôle.

Il existe une troisième matrice, GL_TEXTURE, mais celle-ci n’est jamais utilisée directement.

Toutes sont des matrices 4x4 de float.

Dessiner
Un classique : le triangle

Les fonctions suivantes permettent de dessiner un triangle à l’écran :

glClear(GL_COLOR_BUFFER_BIT); // efface l’écran
glBegin(GL_POLYGON); // début d’un dessin d’un polygone
glColor3f(1.0f, 0.0f, 0.0f); // changement de couleur
glVertex2f(-0.5f, -0.5f); // entrée d’une coordonnée
glColor3f(0.0f, 1.0f, 0.0f);
glVertex2f( 0.5f, -0.5f);
glColor3f(0.0f, 0.0f, 1.0f);
glVertex2f( 0.5f, 0.5f);
glEnd(); // fin du polygone
glFlush(); // affichage

Ce qui donne (imprimé en noir & blanc c’est moche, mais tant pis) :

Les Buffers

Il existe dans OpenGL des « buffers », qui d’une manière générale ne sont que des espaces mémoires contenant des données propres à l’OpenGL.
Le principal est le « COLOR_BUFFER », qui correspond à ce que l’on voit sur l’écran.
La fonction glClear permet de remplir tout un buffer avec la même valeur. La valeur par défaut est 0. Un appel à glClear sur le COLOR_BUFFER rempli l’écran de noir. GlClearColor permet de changer la couleur de remplissage.

Un autre buffer, le back-buffer, fonctionne différemment : pour ne pas voir se dessiner la scène à l’écran, on utilise la technique du « double-buffering ». Le COLOR_BUFFER est en fait dédoublé : l’un est affiché (front-buffer), l’autre est celui sur lequel on travaille (back-buffer). A chaque fois qu’une image est calculée, on inverse (swap) les buffers. Pour le programmeur, tout marche comme s’il dessinait toujours sur le même buffer. L’avantage est que l’utilisateur ne vois pas se dessiner l’image.

Les autres buffers standards sont le Depth-buffer, le Stencil-buffer, l’Accumulation-buffer. Chaque buffer joue un rôle précis, mais ils ne seront présentés que lorsque nous en aurons besoin. D’autres sont des extensions propriétaires (T-Buffer, W-Buffer, …).

Le début de l’entrée d’une primitive se fait avec glBegin : ici un polygone. Les possibilités sont (tirées de la man page de glBegin) :
GL_POINTS Treats each vertex as a single point. Vertex n defines point n.
N points are drawn.

GL_LINES Treates each pair of vertices as an independent line segment.
Vertices 2n-1 and 2n define line n. N/2 lines are drawn.

GL_LINE_STRIP
Draws a connected group of line segments from the first vertex to
the last. Vertices n and n+1 define line n. N-1 lines are
drawn.

GL_LINE_LOOP
Draws a connected group of line segments from the first vertex to
the last, then back to the first. Vertices n and n+1 define line
n. The last line, however, is defined by vertices N and 1. N
lines are drawn.

GL_TRIANGLES
Treates each triplet of vertices as an independent triangle.
Vertices 3n-2, 3n-1, and 3n define triangle n. N/3 triangles are
drawn.

GL_TRIANGLE_STRIP
Draws a connected group of triangles. One triangle is defined
for each vertex presented after the first two vertices. For odd
n, vertices n, n+1, and n+2 define triangle n. For even n,
vertices n+1, n, and n+2 define triangle n. N-2 triangles are
drawn.

GL_TRIANGLE_FAN
Draws a connected group of triangles. One triangle is defined
for each vertex presented after the first two vertices. Vertices
1, n+1, and n+2 define triangle n. N-2 triangles are drawn.

GL_QUADS Treats each group of four vertices as an independent
quadrilateral. Vertices 4n-3, 4n-2, 4n-1, and 4n define
quadrilateral n. N/4 quadrilaterals are drawn.

GL_QUAD_STRIP
Draws a connected group of quadrilaterals. One quadrilateral is
defined for each pair of vertices presented after the first pair.
Vertices 2n-1, 2n, 2n+2, and 2n+1 define quadrilateral n. N/2-1
quadrilaterals are drawn. Note that the order in which vertices
are used to construct a quadrilateral from strip data is
different from that used with independent data.

GL_POLYGON
Draws a single, convex polygon. Vertices 1 through N define this
polygon.

glVertex permet d’entrer un point.
Le format général des fonctions d’OpenGL est : {nom}{1234}{b s i d ub us ui}{v}, respectivement le nom de la fonction, le nombre de paramètres, leur type, passé directement ou par pointeur (vecteur). Tous les caractéristiques de la fonction sont optionnelles sauf le nom.
glVertex existe par exemple sous les formes glVertex2d, glVertex2f, glVertex2i, glVertex2s, glVertex3d, glVertex3f, glVertex3i, glVertex3s, glVertex4d, glVertex4f, glVertex4i, glVertex4s, glVertex2dv, glVertex2fv, glVertex2iv, glVertex2sv, glVertex3dv, glVertex3fv, glVertex3iv…
glColor fixe la couleur courante. De même elle existe sous de nombreuses formes.
glEnd indique la fin de l’entrée de coordonnées.

Cependant jusqu’ici rien n’est affiché : OpenGL a mis tout ce qu’il y a à faire dans un buffer, mais ne l’affiche que lorsqu’on lui demande, avec glFlush, qui rend la main immédiatement, ou avec glFinish, qui attend que tout soit affiché (peu d’intêret).

Bouger le triangle, utilisation de la matrice de vue
Rotation et translation

Pour bouger la « caméra » ou un objet (c’est la même chose de bouger tout les objets ou la caméra), il faut utiliser la matrice de vue (GL_MODELVIEW).
Comme calculer directement une matrice de changement de repère n’est pas vraiment évidente, il existe des fonctions prédéfinies : glRotate et glTranslate.

void glRotated( GLdouble angle, GLdouble x, GLdouble y, GLdouble z )
void glRotatef( GLfloat angle, GLfloat x, GLfloat y, GLfloat z )

void glTranslated( GLdouble x, GLdouble y, GLdouble z )
void glTranslatef( GLfloat x, GLfloat y, GLfloat z )

Ces fonctions multiplient à droite la matrice de vue. Il faut donc faire attention à l’ordre dans lequel les transformations sont entrées : c’est la dernière transformation entrée qui sera appliquée en premier.

Rappels :
soient f de matrice L, g de matrice M et h de matrice N. h°g°f a pour matrice NML donc il faut entrer les matrices dans l'ordre N,M,L
les rotations et les translations ne sont pas commutatives

Par exemple, pour faire tourner un objet sur lui-même, il faut entrer en dernier la matrice de rotation. Pour simplifier, l’origine du repère local d’un objet est par convention son centre de rotation (d’inertie) pour permettre de le faire tourner sur lui-même sans calcul complexe.

Tout cela peut paraître compliqué, mais un ordinateur ne sait comprendre que les matrices, et un énorme avantage est le temps de calcul : même si de nombreuses transformations sont entrées, il ne reste qu’une seule matrice à appliquer à tout les points entrés.

Pile de matrice

Mais comment inverser les transformations entrées ? Calculer les inverses est une perte de temps. Une pile est en effet associée à chaque matrice. Les fonctions sont :

void glPushMatrix( void )
void glPopMatrix( void )

qui respectivement empile et dépile la matrice courante. Même s’il n’y a que deux fonctions, il existe bien trois piles différentes (une par matrice). Comme pour les transformations, seule la matrice active peut-être empilée / dépilée (ce qui est logique, puisque que une matrice est empilée pour la sauvegarder avant une transformation).

Le plus souvent, un objet est caractérisé par ses angles de rotation (ax, ay, az) et sa position (x, y, z). Son affichage est alors du type :

glPushMatrix() ;
glTranslate3f(x,y,z) ;
glRotate3f(ax,ay,az) ;
glBegin(…)
…
glEnd() ;
glPopMatrix() ;

Exemple d’utilisation : un soleil avec ses satellites, et les lunes des satellites. Le principe est le même :
Dessin du soleil
PushMatrix
Translation dans le repère local d’une planète
Dessin de la planète
PushMatrix
Translation dans le repère local d’une lune
Dessin de la lune
PopMatrix
PushMatrix
Translation dans le repère local d’une autre lune
Dessin de la lune
PopMatrix
PopMatrix
PushMatrix
Translation dans le repère local d’une autre planète
Dessin de la planète
PushMatrix
Translation dans le repère local d’une lune
Dessin de la lune
PopMatrix
PopMatrix
etc.

Dessiner en 3D
Initialisation

Quelques changements dans l’initialisation :
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45, aspect, 1.0f, 10.0f);
gluLookAt(0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f);
glEnable(GL_DEPTH_TEST);

gluPerspective construit une matrice de projection de champ de vue de 45°, de rapport largeur/hauteur aspect, de profondeur de champ (1.0, 10.0), ie seuls les objets situés à une distance comprise entre 1.0 et 10. de la caméra sont affichés.
gluLookAt construit une matrice de changement de repère à partir de trois vecteurs : la position de la caméra, la direction où elle regarde, un vecteur indiquant le « haut » (juste la direction, il sera modifié pour faire un repère orthonormé).

Exemple : un Cube

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
total %= 360*500; // temps écoulé en ms
glPushMatrix();
glTranslatef(0.0f, 5.0f+(GLfloat)sin(GLfloat(total)/500.0f)*3.0f, 0.0f);
glRotatef(total/50.0f, 0.3f, 0.5f, 0.8f);
glBegin(GL_QUADS);
glColor3f(1.0f, 0.0f, 0.0f);
glVertex3f(-0.5f, -0.5f, -0.5f);
glVertex3f( 0.5f, -0.5f, -0.5f);
glVertex3f( 0.5f, 0.5f, -0.5f);
glVertex3f(-0.5f, 0.5f, -0.5f);
glColor3f(1.0f, 1.0f, 0.0f);
glVertex3f(-0.5f, -0.5f, 0.5f);
glVertex3f( 0.5f, -0.5f, 0.5f);
glVertex3f( 0.5f, 0.5f, 0.5f);
glVertex3f(-0.5f, 0.5f, 0.5f);
glColor3f(0.0f, 0.0f, 1.0f);

glVertex3f( 0.5f, -0.5f, 0.5f);
glVertex3f( 0.5f, -0.5f, -0.5f);
glVertex3f( 0.5f, 0.5f, -0.5f);
glVertex3f( 0.5f, 0.5f, 0.5f);
glColor3f(0.0f, 1.0f, 1.0f);
glVertex3f(-0.5f, -0.5f, 0.5f);
glVertex3f(-0.5f, -0.5f, -0.5f);
glVertex3f(-0.5f, 0.5f, -0.5f);
glVertex3f(-0.5f, 0.5f, 0.5f);
glColor3f(1.0f, 0.0f, 1.0f);
glVertex3f(-0.5f, 0.5f, 0.5f);
glVertex3f(-0.5f, 0.5f, -0.5f);
glVertex3f( 0.5f, 0.5f, -0.5f);
glVertex3f( 0.5f, 0.5f, 0.5f);
glColor3f(0.0f, 1.0f, 0.0f);
glVertex3f(-0.5f, -0.5f, 0.5f);
glVertex3f(-0.5f, -0.5f, -0.5f);
glVertex3f( 0.5f, -0.5f, -0.5f);
glVertex3f( 0.5f, -0.5f, 0.5f);
glEnd();
glPopMatrix();

Depth-Buffer

En 3D, tout n’est pas affiché : il y a des faces cachées. Comment déterminer ce qui est affiché ? En affichant ce qui est la plus proche de la caméra.
C’est la rôle du depth-buffer : il sauvegarde la distance du point actuellement affiché. Si un point plus proche arrive ensuite, il remplace le point et la valeur dans la depth-buffer est changée. Le depth-buffer à les même dimensions que le color-buffer.
Le depth-buffer doit être effacé avec glClear.

Paramètres du rendering
Antialiasing

L’OpenGL est une « state machine » : les paramètres de dessin consistent en une série de variables (très nombreuses, voir les annexes de la spécification OpenGL pour une liste complète).

La plupart sont modifiables par :

void glEnable( GLenum cap )
void glDisable( GLenum cap )

Pour l’antialiasing, les paramètres sont : GL_POINT_SMOOTH, GL_LINE_SMOOTH, et GL_POLYGON_SMOOTH. Le but de l’antialiasing est de rendre apparemment plus lisse les lignes en faisant un dégradé sur les contours des points.

Culling

Le culling consiste à ne dessiner que les triangles dont les points sont dans le sens des aiguilles d’une montre (« clockwise ») ou dans l’autre sens (« counter-clockwise »). L’intérêt est l’optimisation : l’objectif est de toujours dessiner les faces extérieures d’un objet dans le sens contraire des aiguilles d’une montre, et de filtrer les faces cachées avec le culling.
Prenons l’exemple du cube : les faces sont toujours affichées dans le même ordre, peut importe si elles sont visibles ou pas. C’est le depth-buffer qui permet d’afficher les bonnes faces. Cependant le test du depth-buffer est long : il faut calculer la distance de chaque point de chaque face par rapport à la caméra, puis tester le depth pour chaque point (et mettre à jour le depth si le point est plus près). C’est un test beaucoup trop important pour les faces dont on est sûr qu’elles ne seront pas affichées. Il est plus rapide d’éliminer une face entière d’un coup avec le culling.
L’astuce utilisée par le culling pour savoir de quel coté est vue une face consiste à regarder dans quel sens les points des triangles sont dessinés (en OpenGL tout est décomposé en triangles) : dans le sens des aiguilles d’une montre ou dans l’autre sens.

Cette technique est très rapide : en gros il suffit de regarder le signe du produit scalaire de deux cotés d’un triangle. La seule contrainte pour le programmeur est de dessiner dans le bon sens les points.
Le standard est le CCW (counter-clockwise), que ce soit pour les primitives de glu ou glut, ou encore pour 3DS ou Lightwave.

En OpenGL, les fonctions correspondantes sont :

void glCullFace( GLenum mode )
avec mode à GL_FRONT, GL_BACK ou GL_FRONT_AND_BACK, pour indiquer quel coté doit être éliminé au culling.

void glFrontFace( GLenum mode )
avec mode à GL_CW ou GL_CCW pour indiquer quelles sont les faces de devant

Un appel à glEnable / glDisable avec en paramètre GL_CULL_FACE pour activer / désactiver le culling.

Eclairage

Activation

C’est pour ce genre de choses que l’on apprécie la simplicité d’OpenGL, car activer l’éclairage se fait par :

glEnable(GL_LIGHTING) ; // active le calcul de l’éclairage
glEnable(GL_LIGHT0) ; // allume la lampe 0

C’est tout.

Pour allumer une autre lampe, mettre GL_LIGHTi au lieu de GL_LIGHT0. Il y a au minimum 8 lampes disponibles. On a toujours GL_LIGHTi = GL_LIGHT0 + i. Pour éteindre, utiliser glDisable.
Le nombre maximal de lumières est obtenu en appelant :

glGetIntegerv(GL_MAX_LIGHTS, &nb_lights)

Paramètres

Il faut quand même donner un minimum d’information sur la lampe, comme la couleur et la position de la lampe.

Un peu de théorie

Dans la réalité, les sources de lumières sont des volumes (par exemple un filament), et la lumière se réfléchit à l’infini sur tous les objets.
Il est impossible de simuler un tel comportement, surtout quand il faut calculer une image en quelques millisecondes. Des simplifications ont été apportées :
les sources de lumière sont des points
la lumière ne se réfléchit pas sur les objets
Pour plus de réalisme, les sources disposent de plusieurs paramètres qui n’ont pas forcément d’équivalent réel (voir le paragraphe suivant).

Une chose par contre ne change pas : la couleur visible d’un objet dépend à la fois de sa couleur propre et celle de la lampe, et l’intensité de la lumière qu’il reçoit dépend de l’angle de la surface avec la direction vers la source lumineuse.
Tout sur la lampe

Tiré tout droit de la spécification OpenGL :

Paramètre Type Par défaut Description acm Couleur (0,0,0,0) Intensité ambiante : la lumière ambiante est présente partout dans la scène, elle n’a pas de direction dcli Couleur (0,0,0,0) sauf lampe 0 : (1,1,1,1) Intensité de diffusion : c’est l’intensité « réelle » de la source, elle part dans tous les sens depuis la source scli Couleur Idem Intensité du « spot » : comme la lumière diffuse, sauf qu’elle n’est émise qu’à l’intérieur d’un cône Ppli Position (0,0,1,0) Position sdli Direction (0,0,-1) Direction du spot (vecteur) srli Réel 0 Exposant du spot (entre 0 et 128) crli Réel 180 Angle du cône du spot (180 ou entre 0 et 90) k0i Réel 1.0 Atténuation constante (entre 0 et () k1i Réel 0 Atténuation linéaire k2i Réel 0 Atténuation quadrique
Et sur les matériaux

Les mêmes paramètres existent pour spécifier la « réponse » de la surface d’un objet :

Paramètre Type Par défaut Description acm Couleur (0.2,0.2,0.2,1.0) Couleur ambiante dcm Couleur (0.8,0.8,0.8,1.0) Couleur de diffusion scm Couleur (0,0,0,1) Couleur au « spot » ecm Couleur (0,0,0,1) Couleur émise srm Réel 0 Exposant au « spot »
Fonctions OpenGL

Les fonctions suivantes permettent de spécifier tous les paramètres précédents :

Pour une source lumineuse :

void glLightf( Glenum light, Glenum pname, GLfloat param )
void glLighti( Glenum light, Glenum pname, GLint param )

light = GL_LIGHTi
pname = GL_SPOT_EXPONENT, GL_SPOT_CUTOFF, GL_CONSTANT_ATTENUATION, GL_LINEAR_ATTENUATION, ou GL_QUADRATIC_ATTENUATION
et param = la nouvelle valeur

void glLightfv( GLenum light, GLenum pname, const Glfloat *params)
void glLightiv( GLenum light, GLenum pname, const GLint *params)

light = GL_LIGHTi
pname = GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR, GL_POSITION, GL_SPOT_DIRECTION, GL_SPOT_EXPONENT, GL_SPOT_CUTOFF, GL_CONSTANT_ATTENUATION, GL_LINEAR_ATTENUATION, ou GL_QUADRATIC_ATTENUATION
param = la nouvelle valeur

Attention à la position de la lampe : comme toutes les coordonnées entrées, elles dont immédiatement multipliées par la matrice ModelView. Si celle-ci change, il faut repositionner les lampes.

void glLightModelf( GLenum pname, GLfloat param )
void glLightModeli( GLenum pname, GLint param )
void glLightModelfv( GLenum pname, const GLfloat *params )
void glLightModeliv( GLenum pname, const GLint *params )

Pname param GL_LIGHT_MODEL_AMBIENT (fv ou iv seulement) Intensité ambiante de la scène, par défaut (0.2, 0.2, 0.2, 1.0) GL_LIGHT_MODEL_LOCAL_VIEWER 0 : les reflets des spots sont calculés sur l’axe -z
( 0 : par rapport à la position de l’œil GL_LIGHT_MODEL_TWO_SIDE 0 : seule la face « front » est calculée
( 0 : les deux faces d’une surface sont calculées

Pour un matériau :

Les propriétés de surface d’un polygone peuvent être différentes suivant la face que la face est « front » ou « back » (voir glFrontFace, ou le paragraphe sur le culling).

void glMaterialfv( GLenum face, GLenum pname, const GLfloat *params )
void glMaterialiv( GLenum face, GLenum pname, const GLint *params )

La page man est tellement bien faite que je ne vois pas l’intérêt de la refaire :

Face Specifies which face or faces are being updated. Must be one of GL_FRONT, GL_BACK, or GL_FRONT_AND_BACK. Pname Specifies the material parameter of the face or faces that is being updated. Must be one of GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR, GL_EMISSION, GL_SHININESS, GL_AMBIENT_AND_DIFFUSE, or GL_COLOR_INDEXES. Params Specifies a pointer to the value or values that pname will be set to.
glMaterial assigns values to material parameters. There are two matched sets of material parameters. One, the front-facing set, is used to shade points, lines, bitmaps, and all polygons (when two-sided lighting is disabled), or just front-facing polygons (when two-sided lighting is enabled). The other set, back-facing, is used to shade back-facing polygons only when two-sided lighting is enabled. Refer to the glLightModel reference page for details concerning one- and two-sided lighting calculations.
[…]

GL_AMBIENT params contains four integer or floating-point values that specify the ambient RGBA reflectance of the material. Integer values are mapped linearly such that the most positive representable value maps to 1.0, and the most negative representable value maps to -1.0. Floating-point values are mapped directly. Neither integer nor floating-point values are clamped. The default ambient reflectance for both front- and back-facing materials is (0.2, 0.2, 0.2, 1.0). GL_DIFFUSE params contains four integer or floating-point values that specify the diffuse RGBA reflectance of the material. Integer values are mapped linearly such that the most positive representable value maps to 1.0, and the most negative representable value maps to -1.0. Floating-point values are mapped directly. Neither integer nor floating-point values are clamped. The default diffuse reflectance for both front- and back-facing materials is (0.8, 0.8, 0.8, 1.0). GL_SPECULAR params contains four integer or floating-point values that specify the specular RGBA reflectance of the material. Integer values are mapped linearly such that the most positive representable value maps to 1.0, and the most negative representable value maps to -1.0. Floating-point values are mapped directly. Neither integer nor floating-point values are clamped. The default specular reflectance for both front- and back-facing materials is (0.0, 0.0, 0.0, 1.0). GL_EMISSION params contains four integer or floating-point values that specify the RGBA emitted light intensity of the material. Integer values are mapped linearly such that the most positive representable value maps to 1.0, and the most negative representable value maps to -1.0. Floating-point values are mapped directly. Neither integer nor floating-point values are clamped. The default emission intensity for both front- and back-facing materials is (0.0, 0.0, 0.0, 1.0). GL_SHININESS params is a single integer or floating-point value that specifies the RGBA specular exponent of the material. Integer and floating-point values are mapped directly. Only values in the range [0,128] are accepted. The default specular exponent for both front- and back-facing materials is 0. GL_AMBIENT_AND_DIFFUSE Equivalent to calling glMaterial twice with the same parameter values, once with GL_AMBIENT and once with GL_DIFFUSE.
Les normales

Une fois que la source lumineuse est définie, ainsi que les caractéristiques du matériau, il reste encore une chose : l’angle d’incidence de la lumière. Cela ne change pas la couleur d’une surface, mais l’intensité de l’éclairage qu’elle reçoit.
L’angle d’incidence est l’angle entre la normale à une surface et la droite partant de la source lumineuse et passant par le point d’incidence. Il faut impérativement préciser pour chaque point la « normale » de ce point (plus rigoureusement, la normale à la surface dont ce point fait partie).
Le principe est le même que pour glColor, la normale est une des variables d’état d’OpenGL, qui est modifiée par l’une des fonctions suivantes :

void glNormal3b( GLbyte nx, GLbyte ny, GLbyte nz )
void glNormal3d( GLdouble nx, GLdouble ny, GLdouble nz )
void glNormal3f( GLfloat nx, GLfloat ny, GLfloat nz )
void glNormal3i( GLint nx, GLint ny, GLint nz )
void glNormal3s( GLshort nx, GLshort ny, GLshort nz )
void glNormal3bv( const GLbyte *v )
[...]

Les paramètres correspondent au coordonnées du vecteur normal. ATTENTION : il doit être UNITAIRE (de norme 1).
Il est possible de rendre unitaire automatiquement les vecteurs entrés, mais cela entraîne un calcul supplémentaire :
glEnable(GL_NORMALIZE);

Exemple

L’exemple le plus simple :

void init ()
{
glClearColor (0.0f, 0.0f, 0.0f, 0.0f);

GLfloat ambientProperties[] = {0.9f, 0.9f, 0.9f, 1.0f};
GLfloat positionProperties[] = {0.0f, -5.0f, 0.0f, 0.0f};

glLightfv( GL_LIGHT0, GL_DIFFUSE, ambientProperties);
glLightfv( GL_LIGHT0, GL_POSITION, positionProperties);

glEnable(GL_LIGHT0);
glEnable(GL_LIGHTING);
}

void display (void)
{
static timeb tt ,t;
static int total;
GLfloat Rouge[] = {0.8f, 0.0f, 0.0f, 1.0f};
GLfloat Vert[] = {0.0f, 0.8f, 0.0f, 1.0f};
GLfloat Bleu[] = {0.0f, 0.0f, 0.8f, 1.0f};
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

ftime(&t);
total += (t.time - tt.time)*1000 + (t.millitm - tt.millitm);
total %= 360*300;
tt = t;
glPushMatrix();
glRotatef((total/30.0f), 0.5f, 0.3f, 0.8f);
glRotatef((total/50.0f), 0.5f, -0.3f, 0.8f);
glBegin(GL_QUADS);
glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, Rouge);
glNormal3d(0.0,0.0,1.0);
glVertex3d(-1, 1, 1);
glVertex3d(-1, -1, 1);
glVertex3d( 1, -1, 1);
glVertex3d( 1, 1, 1);
glNormal3d(0.0,0.0,-1.0);
glVertex3d( 1, 1, -1);
glVertex3d( 1, -1, -1);
glVertex3d( -1, -1, -1);
glVertex3d( -1, 1, -1);
glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, Vert);
glNormal3d(1.0,0.0,0.0);
glVertex3d( 1, 1, 1);
glVertex3d( 1, -1, 1);
glVertex3d( 1, -1, -1);
glVertex3d( 1, 1, -1);
glNormal3d(-1.0,0.0,0.0);
glVertex3d( -1, 1, 1);
glVertex3d( -1, 1, -1);
glVertex3d( -1, -1, -1);
glVertex3d( -1, -1, 1);
glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, Bleu);
glNormal3d(0.0,-1.0,0.0);
glVertex3d( -1, -1, 1);
glVertex3d( -1, -1, -1);
glVertex3d( 1, -1, -1);
glVertex3d( 1, -1, 1);
glNormal3d(0.0,1.0,0.0);
glVertex3d( -1, 1, 1);
glVertex3d( 1, 1, 1);
glVertex3d( 1, 1, -1);
glVertex3d( -1, 1, -1);
glEnd();
glPopMatrix();
glutSwapBuffers();
}

void reshape (int width, int height)
{
glViewport (0, 0, width, height);

glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
gluPerspective (45.0, ((GLfloat) width) / ((GLfloat) height),1.0,10.0);

glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();
gluLookAt (0.0, -5.0, 0.0,
0.0, 0.0, 0.0,
0.0, 0.0, 1.0);

init();
glEnable(GL_DEPTH_TEST);
glEnable(GL_POLYGON_SMOOTH);

glutPostRedisplay();
}
Pour voir l’effet des différents paramètres, voir les très bons exemples de www.cs.utah.edu/~narobins/tutors/tutor, et le lightlab de www.student.nada.kth.se/~nv91-gta/OpenGL/projects/lightlab/.

Fog (brouillard)

C’est ce qui simule les effets atmosphériques (je ne vous apprends rien là) comme la fumée, la pollution etc. On s’en sert aussi pour rendre les effets de vision limitée (en même temps, cela permet de virer de la scène une partie des objets 3D trop loin et d’accélérer le rendu).

Le fonctionnement du brouillard

Lorsque le brouillard est activé, les objets qui sont de plus en plus loin commencent à se fondre dans la couleur du brouillard. On peut contrôler aussi la densité du brouillard pour déterminer le rythme avec lequel les objets sont estompé lorsque la distance augmente.

Le brouillard est appliqué à la scène après les transformations matricielles, la lumière, et les textures, donc il affecte les objets transformés (ie avec texture…).

L’utilisation du brouillard

C’est très simple. On l’active en passant GL_FOG comme attribut de glEnable() et on la couleur et l’équation de contrôle de la densité du brouillard grâce à :

void glFogf( GLenum pname, GLfloat param )
void glFogi( GLenum pname, GLint param )
void glFogfv( GLenum pname, const GLfloat *params )
void glFogiv( GLenum pname, const GLint *params )

avec pname = GL_FOG_MODE, GL_FOG_DENSITY, GL_FOG_START, GL_FOG_END, ou GL_FOG_COLOR, et param la nouvelle valeur.

Exemple :

GlEnable(GL_FOG) ;

GLfloat fogcolor[4] = {0.5, 0.5, 0.5, 1} ;
GLint fogmode = GL_EXP ;
glFogi (GL_FOG_MODE, fogmode) ;
glFogfv(GL_FOG_COLOR, fogcolor) ;
glFogf(GL_FOG_DENSITY, 0.35) ;
glFogf(GL_FOG_START, 1.0) ;
glFogf(GL_FOG_END, 5.0) ;

Les équations

Le brouillard se sert d’une variable f variant de 0 (brouillard complet) à 1 (transparent comme de l’eau de roche). Les 3 équations sont :

GL_LINEAR : f = (end - z) / (end - start)
GL_EXP : f = e-density * z
GL_EXP2 : f = e-(density * z)²

Transparence et alpha-blending

Plusieurs fois déjà des couleurs ont été présentée sous forme RGBA : rouge, vert, bleu et alpha. Si les trois premières coordonnées sont évidentes, la quatrième quant à elle sert à indiquer la « transparence ».

Activation

Si pour l’instant rien n’a été dit sur les valeurs de l’alpha, c’est simplement parce que par défaut OpenGL s’en fiche.
Pour activer le canal alpha, il faut appeler comme d’habitude glEnable :

glEnable(GL_BLEND) ;
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) ; // expliqué après

Ensuite il suffit de changer la valeur de A suivant la transparence.

Exemple

void init ()
{
glClearColor (0.0f, 0.0f, 0.0f, 0.0f);
glEnable(GL_BLEND) ;
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}

void display (void)
{
static timeb tt ,t;
static int total;
GLfloat Rouge[] = {0.8f, 0.0f, 0.0f, 0.5f};
GLfloat Vert[] = {0.0f, 0.8f, 0.0f, 0.5f};
GLfloat Bleu[] = {0.0f, 0.0f, 0.8f, 0.5f};

// […]

glBegin(GL_QUADS);
glColor4fv(Rouge);
// […] (dessin cube)
glColor4fv(Vert);
// […]
glColor4fv(Bleu);
// […]
glEnd();
glPopMatrix();
glutSwapBuffers();
}

Paramètres

La paramétrage principal est effectué par la fonction :

void glBlendFunc( GLenum sfactor, GLenum dfactor )

Si la fonction s’appelle « blend » (« mélanger » en anglais, ici en parlant des couleurs) et pas « transparency », c’est que la transparence n’est qu’une application de l’alpha-blending. C’est de loin la principale.
Néanmoins ce que fait réellement l’alpha-blending, c’est mélanger la couleur déjà affichée avec une nouvelle. Et comme dans OpenGL, la rapidité du rendu est prioritaire sur la spécialisation des fonctions, il est bon de savoir comment cela fonctionne.
Le mélange est précisé par un appel à glBlendFunc, avec le premier paramètre indiquant par quelle valeur multiplier (canal par canal) la nouvelle couleur, le second la couleur déjà affichée. La nouvelle couleur affichée est la somme de ces deux multiplications.
L’indice « s » indique que la nouvelle couleur est concernée, « d » qu’il s’agit de la couleur déjà affichée.

La couleur affichée est : C = CsS + CdD. S et D étant paramétrés respectivement par sfactor et dfactor.

Principales valeurs de S en fonction de sfactor :

Valeur de sfactor Valeur de S ZERO (0,0,0,0) ONE (1,1,1,1) DST_COLOR (Rd,Gd,Bd,Ad) ONE_MINUS_DST_COLOR (1,1,1,1) - (Rd,Gd,Bd,Ad) SRC_ALPHA (As, As, As, As) ONE_MINUS_SRC_ALPHA (1,1,1,1) - (As,As,As,As) DST_ALPHA (Ad,Ad,Ad,Ad) ONE_MINUS_DST_ALPHA (1,1,1,1) - (Ad,Ad,Ad,Ad) SRC_ALPHA_SATURATE (f,f,f,1) avec f = min (As, 1 - Ad)
Principales valeurs de D en fonction de dfactor :

Valeur de sfactor Valeur de D ZERO (0,0,0,0) ONE (1,1,1,1) SRC_COLOR (Rs,Gs,Bs,As) ONE_MINUS_SRC_COLOR (1,1,1,1) - (Rs,Gs,Bs,As) SRC_ALPHA (As, As, As, As) ONE_MINUS_SRC_ALPHA (1,1,1,1) - (As,As,As,As) DST_ALPHA (Ad, Ad, Ad, Ad) ONE_MINUS_DST_ALPHA (1,1,1,1) - (Ad,Ad,Ad,Ad) SRC_ALPHA_SATURATE (f,f,f,1) avec f = min (As, 1 - Ad)
Les paramètres les plus courants sont (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA).
Cela représente bien la transparence codée dans le canal Alpha : la couleur affichée est multipliée par la valeur de transparence (si 0, complètement transparent, si 1 opaque), et la couleur en dessous est multipliée par le complément.

Ordre des primitives et depth-buffer

Le problème avec cette manière de rendre les objets transparents, c’est qu’il faut dessiner les surfaces dans l’ordre d’apparition pour avoir un meilleur rendu. En plus, si le depth-buffer est activé, les surfaces recouvertes sont directement éliminées au lieu d’être transparentes. Le plus simple, c’est de désactiver avec glDisable(GL_DEPTH_TEST) le depth-buffer lorsque des surfaces transparentes sont affichées.

Listes
Commandes OpenGL

Certains objets sont toujours les mêmes. Comme OpenGL traduit toutes les séquences entrées, il est intéressant de créer des listes pour regrouper les instructions. Cela accélère légèrement l’affichage et réduit le code. Une liste est alors codée par un entier (un seul entier est alors envoyé pour un objet quelconque).

En pratique :
d’abord réserver des numéros de listes avec : GLuint glGenLists( GLsizei range ) où range est le nombre de numéros de listes à réserver. Les numéros réservés vont de la valeur renvoyée à cette valeur + range.
ensuite appeler : void glNewList( GLuint list, GLenum mode ) avec pour list le numéro de la liste à créer, et mode = GL_COMPILE ou GL_COMPILE_AND_EXECUTE. Les deux modes créés la liste, mais le second en plus l’éxécute (l’affiche).
mettre toutes les routines souhaitées (glBegin, glColor, glVertex…), elles sont enregistrées dans la liste. Certaines commandes ne peuvent pas être enregistrées dans une liste, mais il y en a peu et c’est plutôt logique (par exemple glGenList, glFlush, glNewlist…).
terminer par : void glEndList( void )

Pour afficher une liste, il faut appeler :
void glCallList( GLuint list )
ou
void glCallLists( GLsizei n, GLenum type, const GLvoid *lists)
pour afficher plusieurs listes d’un coup (type indique le type du tableau passé en paramètre : GL_BYTE, GL_INT…).

Reste à effacer une liste lorsqu’elle n’est plus utile :
void glDeleteLists( GLuint list, GLsizei range )
dont les paramètres sont les même que glGenList.

Exemple

Par exemple le cube peut être mis dans une liste :

void init()
{
GLfloat Rouge[] = {0.8f, 0.0f, 0.0f, 0.5f};
GLfloat Vert[] = {0.0f, 0.8f, 0.0f, 0.5f};
GLfloat Bleu[] = {0.0f, 0.0f, 0.8f, 0.5f};

cube = glGenLists(1);
glNewList(cube, GL_COMPILE);
glBegin(GL_QUADS);
glColor4fv(Rouge);
glNormal3d(0.0,0.0,1.0);
glVertex3d(-1, 1, 1);
glVertex3d(-1, -1, 1);
glVertex3d( 1, -1, 1);
glVertex3d( 1, 1, 1);
glNormal3d(0.0,0.0,-1.0);
glVertex3d( 1, 1, -1);
// […]
glEnd();
glEndList();
}

void display (void)
{
// […]
glPushMatrix();
glRotatef((total/30.0f), 0.5f, 0.3f, 0.8f);
glRotatef((total/50.0f), 0.5f, -0.3f, 0.8f);
glCallList(cube);
glPopMatrix();
glutSwapBuffers();
}

Textures

Jusqu’ici, les surfaces étaient simplement coloriées, ce qui ne permet pas d’avoir des rendus très réalistes. Les textures améliore considérablement le rendu. Le principe est de remplir une surface avec une image bitmap correspondant à une photo de la surface.

Principe général

Un texture est appliquée à une surface de la manière suivante :
d’abord il faut indiquer quelle image il va falloir appliquer (ce qui revient à donner un pointeur vers l’image).
ensuite, avant d’entrer chaque point (vertex) de la surface, il faut préciser où se situe ce point dans la texture, c’est le même principe qu’avant avec glColor. Cela revient en quelque sorte à mettre des épingles sur la texture

OpenGL se charge ensuite d’appliquer la texture, en faisant correspondre les vertex avec les « épingles ».

En OpenGL

Dans ce cours, les routines de chargement d’images en mémoire ne seront pas expliquées. Ce dont à besoin OpenGL, c’est d’une image non compressée chargée sous la forme d’un tableau en mémoire. Il existe de nombreuses routines toutes faites pour charger les principaux formats d’images (BMP, PCX, GIF, PNG…).

Spécifier l’image

Pour spécifier la texture à utiliser, appeler :
void glTexImage2D( GLenum target, GLint level, GLint components, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels )

Avec :

Target GL_TEXTUTE_2D Level Indique le niveau de mipmapping, expliqué plus loin dans ce cours Components Nombre de composantes dans la texture (1, 2, 3 ou 4). Généralement 3 (pour RGB) ou 4 (pour RGBA). Width Largeur de l’image. Doit être 2n + 2 (dépend de bordure). Height Hauteur de l’image. Doit être 2m + 2 (dépend de bordure). Bordure Indique la largeur de la bordure : 0 ou 1 Format Codage des couleurs : GL_COLOR_INDEX, GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_RGB, GL_RGBA, GL_LUMINANCE, ou GL_LUMINANCE_ALPHA Type Type du tableau : GL_UNSIGNED_BYTE, GL_BYTE, GL_BITMAP, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT, GL_INT, ou GL_FLOAT Pixel Pointeur vers l’image.
Les « épingles »

La première chose à faire, c’est d’activer les textures :

glEnable(GL_TEXTURE_2D) ;

Pour poser une épingle, avant un vertex il faut appeler :

void glTexCoord2f( GLfloat s, GLfloat t )

ou l’une de ses variantes.

Les coordonnées (s, t) indiquent la correspondance entre le prochain vertex entré et la texture. En OpenGL, le repère d’une texture fait toujours [0..1] sur [0..1], mais s et t peuvent sortir des ces intervalles, l’effet dépend alors du paramètrage choisi.

Paramétrage

Application de la texture
Les paramètres d’application d’une texture sont :

Nom Type Valeur possibles Signification GL_TEXTURE_WRAP_S Entier GL_CLAMP, GL_CLAMP_TO_EDGE, GL_REPEAT Indique ce qui se passe si une coordonnée s sort de l’intervalle [0..1]. Soit la texture est répètée (mosaïque), soit seulement le bord est répété. GL_TEXTURE_WRAP_T Entier Idem Idem pout t GL_TEXTURE_MIN_FILTER Entier NEAREST, LINEAR, NEAREST_MIPMAP_NEAREST, NEAREST_MIPMAP_LINEAR, LINEAR_MIPMAP_NEAREST, LINEAR_MIPMAP_LINEAR Indique la qualité de l’agrandissement d’une texture. LINEAR est meilleur que NEAREST, mais plus lent. GL_TEXTURE_MAG_FILTER Entier NEAREST, LINEAR Idem en réduction
Il existe d’autres paramètres, mais ceux-ci sont de loin les plus importants.

Pour changer un paramètre, il faut appeler :

void glTexParameteri(GLenum target, GLenum pname, GLint param )

avec target = GL_TEXTURE_2D, pname l’un des noms de paramètres, et param la nouvelle valeur.

glColor

La couleur courante (spécifiée par glColor), joue un rôle différent suivant les paramètres passés à :

void glTexEnvi( GLenum target, GLenum pname, Glint param )

avec target = GL_TEXTURE_ENV, pname = GL_TEXTURE_ENV_MODE, et param = GL_REPLACE, GL_MODULATE, GL_DECAL, ou GL_BLEND.

Il y a beaucoup de cas suivant le nombre de composantes de la texture.
Dans le cas de 3 ou 4 composantes (RGB ou RGBA), les modes les plus courants sont :
GL_REPLACE, la couleur courante n’intervient pas
GL_MODULATE, la couleur courante multiplie la texture. Il est alors possible de changer très facilement l’intensité d’une texture.

Exemple

Pour simplifier (et garantir la portabilité), le format d’image est ici le « Raw », c’est-à-dire l’image seule. Paint Shop Pro gère très bien ce format. Le problème est que la taille de l’image n’est pas inclus dans ce fichier.

void init()
{
char buffer[32*32*3];
FILE *f = fopen("../Data/Marbre.raw", "rb");
if (f)
{
fread(buffer, 3072, 1, f);
fclose(f);

glTexImage2D(GL_TEXTURE_2D,0,3, 32, 32, 0, GL_RGB, GL_UNSIGNED_BYTE, buffer);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glEnable(GL_TEXTURE_2D);
}

cube = glGenLists(1);
glNewList(cube, GL_COMPILE);
glBegin(GL_QUADS);
glNormal3d(0.0,0.0,1.0);
glTexCoord2f(0.0, 1.0); glVertex3d(-1, 1, 1);
glTexCoord2f(1.0, 1.0); glVertex3d(-1, -1, 1);
glTexCoord2f(1.0, 0.0); glVertex3d( 1, -1, 1);
glTexCoord2f(0.0, 0.0); glVertex3d( 1, 1, 1);
glNormal3d(0.0,0.0,-1.0);
glTexCoord2f(0.0, 1.0); glVertex3d( 1, 1, -1);
glTexCoord2f(0.0, 0.0); glVertex3d( 1, -1, -1);
glTexCoord2f(1.0, 0.0); glVertex3d( -1, -1, -1);
glTexCoord2f(1.0, 1.0); glVertex3d( -1, 1, -1);
glNormal3d(1.0,0.0,0.0);
glTexCoord2f(0.0, 0.0); glVertex3d( 1, 1, 1);
glTexCoord2f(1.0, 0.0); glVertex3d( 1, -1, 1);
glTexCoord2f(1.0, 1.0); glVertex3d( 1, -1, -1);
glTexCoord2f(0.0, 1.0); glVertex3d( 1, 1, -1);
glNormal3d(-1.0,0.0,0.0);
glTexCoord2f(0.0, 0.0); glVertex3d( -1, 1, 1);
glTexCoord2f(1.0, 0.0); glVertex3d( -1, 1, -1);
glTexCoord2f(1.0, 1.0); glVertex3d( -1, -1, -1);
glTexCoord2f(0.0, 1.0); glVertex3d( -1, -1, 1);
glNormal3d(0.0,-1.0,0.0);
glTexCoord2f(0.0, 0.0); glVertex3d( -1, -1, 1);
glTexCoord2f(1.0, 0.0); glVertex3d( -1, -1, -1);
glTexCoord2f(1.0, 1.0); glVertex3d( 1, -1, -1);
glTexCoord2f(0.0, 1.0); glVertex3d( 1, -1, 1);
glNormal3d(0.0,1.0,0.0);
glTexCoord2f(0.0, 0.0); glVertex3d( -1, 1, 1);
glTexCoord2f(1.0, 0.0); glVertex3d( 1, 1, 1);
glTexCoord2f(1.0, 1.0); glVertex3d( 1, 1, -1);
glTexCoord2f(0.0, 1.0); glVertex3d( -1, 1, -1);
glEnd();
glEndList();
}

MipMapping

Le MipMapping consiste à avoir la même texture à plusieurs échelles, de façon à limiter les transferts mémoires lors du plaquage de texture (inutile de transférer une texture 256x256 si l’objet dessiné est très loin dans le décor et ne fait plus que du 5x5).

En théorie, le programmeur devrait avoir 3 ou 4 fois la même texture, mais avec une résolution différente. Il faut alors entrer toutes les textures à chaque fois, en changeant le paramètre level de glTextImage2D (sachant que 0 est l’image la plus grande, et les suivantes sont ses réductions). Lorsque la texture est appliqué, OpenGL soit choisi l’image de taille la plus proche de la surface à afficher (NEAREST_MIPMAP), soit fait une sorte de moyenne entre l’image de taille juste au dessus et celle juste en dessous (LINEAR_MIPMAP).

En pratique, il y a une fonction glu :

int gluBuild2DMipmaps(GLenum target, GLint components, GLint width, GLint height, GLenum format, GLenum type, const void *data )

Les paramètres sont les mêmes que pour glTexImage2D, sauf qu’il est cette fois possible d’utiliser des images de dimensions qui ne sont pas des puissances de 2. L’image passée est celle de meilleure qualité (level = 0).

Chargement dans la carte vidéo

L’intérêt du MipMapping est très limité si les textures ne sont pas stockées dans la carte vidéo.
Le principe est similaire à celui des listes :

void glGenTextures( GLsizei n, GLuint * textures)

permet de réserver n textures, les numéros sont alors placés dans le tableau pointé par textures.
Ensuite le numéro de la texture courante est changé par :

void glBindTexture( GLenum target, GLuint texture)

avec target = GL_TEXTURE_2D et texture le numéro de la texture à activer. Ensuite toutes les fonctions faisant intervenir une texture utilisent la texture active.

Les textures sont libérées par :

void glDeleteTextures(GLsizei n, const GLuint * textures )

Pour résumé, une texture est le plus souvent chargée par :

glGenTextures(1, &Texture_num);
glBindTexture(GL_TEXTURE_2D, Texture_num);
gluBuild2DMipmaps(GL_TEXTURE_2D, 3, width, height(), GL_RGB, GL_UNSIGNED_BYTE, data);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);

Ensuite pour activer une texture, avant un glBegin :

glBindTexture(GL_TEXTURE_2D, Texture_num);

Exemple

void init()
{
char buffer[32*32*3];
FILE *f = fopen("../Data/Marbre.raw", "rb");
if (f)
{
fread(buffer, 3072, 1, f);
fclose(f);
glGenTextures(1, &Texture1);
glBindTexture(GL_TEXTURE_2D, Texture1);
glTexImage2D(GL_TEXTURE_2D,0,3, 32, 32, 0, GL_RGB, GL_UNSIGNED_BYTE, buffer);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glEnable(GL_TEXTURE_2D);
}
f = fopen("../Data/Marbre2.raw", "rb");
if (f)
{
fread(buffer, 3072, 1, f);
fclose(f);
glGenTextures(1, &Texture2);
glBindTexture(GL_TEXTURE_2D, Texture2);
glTexImage2D(GL_TEXTURE_2D,0,3, 32, 32, 0, GL_RGB, GL_UNSIGNED_BYTE, buffer);
}

cube = glGenLists(1);
glNewList(cube, GL_COMPILE);
glBindTexture(GL_TEXTURE_2D, Texture2);
glBegin(GL_QUADS);
glNormal3d(0.0,0.0,1.0);
glTexCoord2f(0.0, 1.0); glVertex3d(-1, 1, 1);
glTexCoord2f(1.0, 1.0); glVertex3d(-1, -1, 1);
glTexCoord2f(1.0, 0.0); glVertex3d( 1, -1, 1);
glTexCoord2f(0.0, 0.0); glVertex3d( 1, 1, 1);
glNormal3d(0.0,0.0,-1.0);
glTexCoord2f(1.0, 0.0); glVertex3d( 1, 1, -1);
glTexCoord2f(1.0, 1.0); glVertex3d( 1, -1, -1);
glTexCoord2f(0.0, 1.0); glVertex3d( -1, -1, -1);
glTexCoord2f(0.0, 0.0); glVertex3d( -1, 1, -1);
glNormal3d(1.0,0.0,0.0);
glTexCoord2f(1.0, 1.0); glVertex3d( 1, 1, 1);
glTexCoord2f(0.0, 1.0); glVertex3d( 1, -1, 1);
glTexCoord2f(0.0, 0.0); glVertex3d( 1, -1, -1);
glTexCoord2f(1.0, 0.0); glVertex3d( 1, 1, -1);
glNormal3d(-1.0,0.0,0.0);
glTexCoord2f(0.0, 0.0); glVertex3d( -1, 1, 1);
glTexCoord2f(1.0, 0.0); glVertex3d( -1, 1, -1);
glTexCoord2f(1.0, 1.0); glVertex3d( -1, -1, -1);
glTexCoord2f(0.0, 1.0); glVertex3d( -1, -1, 1);
glEnd();
glBindTexture(GL_TEXTURE_2D, Texture1);
glBegin(GL_QUADS);
glNormal3d(0.0,-1.0,0.0);
glTexCoord2f(0.0, 0.0); glVertex3d( -1, -1, 1);
glTexCoord2f(1.0, 0.0); glVertex3d( -1, -1, -1);
glTexCoord2f(1.0, 1.0); glVertex3d( 1, -1, -1);
glTexCoord2f(0.0, 1.0); glVertex3d( 1, -1, 1);
glNormal3d(0.0,1.0,0.0);
glTexCoord2f(0.0, 0.0); glVertex3d( -1, 1, 1);
glTexCoord2f(1.0, 0.0); glVertex3d( 1, 1, 1);
glTexCoord2f(1.0, 1.0); glVertex3d( 1, 1, -1);
glTexCoord2f(0.0, 1.0); glVertex3d( -1, 1, -1);
glEnd();
glEndList();
}

Priorités des textures

Comme il se peut que toutes les textures ne rentrent pas dans la carte vidéo, il est possible de donner des priorités au chargement des textures en mémoire vidéo.

void glPrioritizeTextures( GLsizei n, GLuint * textures, GLclampf * priorities)

avec priorities un tableau de n flottants appartenant à [0..1], 0 étant la priorité la plus faible et 1 la plus forte.

Les opérations sur les pixels
Introduction

Tout ce qu’on va voir ici concerne l’affichage de bitmaps, la copie et l’affichage de blocs de pixels et la façon dont les pixels sont stockés en mémoire.

L’utilisation la plus courante en OpenGL du transfert de pixels est l’ajout d’images qui recouvrent la scène (photo d’un cockpit, nom du joueur, score…). Il est plus facile des les afficher en utilisant les coordonnées écrans (par exemple 800x600), il faut alors repasser en mode 2D (ce qui ne fait que changer la matrice de projection, tout ce qui est 3D ne bouge pas) :

glMatrixMode(GL_PROJECTION) ;
glLoadIdentity() ;
gluOrtho2D(0.0, (Glfloat) width, 0.0, (Glfloat) height) ;
glMatrixMode(GL_MODEL_VIEW) ;
glLoadIdentity() ;

La Raster Position

La position actuelle de rastérisation est l’origine du prochain bitmap affiché. Le bitmap est affiché au dessus et à droite de cette position. Exemple :

glRasterPos2i(20, 20) ;

La façon de trouver la position est la même que celle de glVertex dans le cas ou l’environnement est en 3D. Si la position est hors du clipping (zone d’affichage), la position est dite invalide. Pour obtenir la Raster Position actuelle et sa validité , on utilise :
GLfloat vector[4] ;
glGetFloatv(GL_CURRENT_RASTER_POSITION, vector) ;
glGetBooleanv(GL_CURRENT_RASTER_POSITION_VALID) ;

Dessiner le bitmap

Après avoir choisi la Raster Position, on peut dessiner le bitmap grâce à l’instruction glBitmap dont le prototype est :

void glBitmap (GLsizei width, GLsizei height, GLfloat Xbo Ybo Xbi Ybi, const GLubyte *bitmap) ;

Si la Raster Position est invalide, rien n’est affiché. On utilise Xbo et Ybo pour définir l’origine du bitmap par rapport à la Raster Position. Xbi et Ybi sont les incréments à la Raster Position après avoir affiché le bitmap. Pour choisir la couleur d’affichage du bitmap, on utilise glColor juste avant.
Le gros problème de cette fonction est qu’elle ne gère pas le format RGB ou RGBA (seulement le mode 8 bits). Les fonctions des paragraphes suivants sont un peu plus lourdes à manipuler, mais plus générales.

Lire, écrire et copier des images

Pour lire une image, la fonction glReadPixels doit être utilisée :

void glReadPixels (Glint x, y, Glsizei width, height, Glenum format, type, Glvoid *pixels) ;

x et y représentent le coin bas-gauche de l’image à lire. Width et height sont ses dimensions. Le format et le type servent à définir le type de structure qui sera utilisé pour le stockage. Les différents types de format sont :

Format
GL_COLOR_INDEX
GL_RGB Dans l’ordre : Rouge, Vert, Bleu
GL_RGBA RGB et après Alpha
GL_RED
GL_GREEN
GL_BLUE
GL_ALPHA
GL_LUMINANCE
GL_LUMINANCE_ALPHA luminance suivi de alpha
GL_STENCIL_INDEX
GL_DEPTH_COMPONENT

Type
GL_UNSIGNED_BYTE (8bits)
GL_BYTE (8 bits)
GL_BITMAP (même format que pour glBitmap)
GL_UNSIGNED_SHORT (16 bits)
GL_SHORT (16 bits)
GL_UNSIGNED_INT (32 bits)
GL_INT (32 bits)
GL_FLOAT (flottant en simple précision)

! Il faut bien faire attention de faire correspondre le type de *pixel avec celui indiqué par le format et le type.

Pour écrire une image, on utilise la fonction glDrawPixel :

void glDrawPixels(Glsizei widht, height, Glenum format, type, const Glvoid *pixels) ;

Cela dessine un rectangle contenant l’image pointée par *pixels à la Raster Position et de la taille widht * height. Les valeurs de format et de type sont les mêmes que celles de glReadPixels.

La fonction glCopyPixels permet de copier un rectangle d’un buffer spécifié par le cinquième paramètre (GL_COLOR, GL_STENCIL ou GL_DEPTH) vers la raster position.

void glCopyPixels(Glint x, y, Glsizei width, height, Glenum buffer) ;

Agrandissement, réduction et réflexion

Normalement, chaque pixel d’une image est écrit dans un pixel sur l’écran. La fonction

void glPixelZoom(Glfloat, zoomx, zoomy) ;

permet de modifier la taille que chaque pixel prend sur l’écran. Si on donne 2 pour chaque argument, chaque pixel est représenté à l’écran par 4 pixels. Des valeurs non entières et négatives sont permises. Les facteurs négatifs de zoom font une réflexion par rapport à la raster position.

Tests de validation et stencil buffer

Chaîne de tests

Il est tant de voir la liste complète des tests de validation d’un point :

( Scissor Test ( Alpha Test ( Stencil Test ( Depth Test ( […] ( framebuffer

Scissor test

Le scissor test est paramétré par :

void glScissor( GLint x, GLint y, GLsizei width, GLsizei height )

Cela permet de limiter l’affichage à un rectangle dont le coin haut - gauche à pour coordonnées (x, y) dans la fenêtre. Par défaut (x, y) = (0, 0) et width et height ont la même taille que la fenêtre.
Le test est activé par : glEnable(GL_SCISSOR_TEST)

Alpha test

Un point peut être éliminé par sa valeur alpha avec :

void glAlphaFunc( GLenum func, GLclampf ref )

avec func = GL_NEVER, GL_LESS, GL_EQUAL, GL_LEQUAL, GL_GREATER, GL_NOTEQUAL, GL_GEQUAL, ou GL_ALWAYS
Ne pas oublier, comme toujours : glEnable(GL_ALPHA_TEST)

Stencil test

Ce test est plus complexe, mais plus utile que les deux précédents.

Tout comme le depth-buffer, le stencil-buffer est un espace mémoire de la taille du framebuffer. Mais cette fois son utilisation est libre.
Sous glut, ne pas oublier de le rajouter à l’initialisation :

glutInitDisplayMode (GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH | GLUT_STENCIL)

Effacement

D’abord, le stencil étant comme le depth un buffer, il faut (le plus souvent) l’effacer à chaque image avec :

glClear(… | GL_STENCIL_BUFFER_BIT) ;

La valeur de remplissage est paramétrable avec :

void glClearStencil( GLint s )

Initialisation

Comme pour tous les tests, il faut appeler :
glEnable(GL_STENCIL_TEST) ;

Le test est paramétré par deux fonctions :

void glStencilFunc( GLenum func, GLint ref, GLuint mask)
qui sert à indiquer le test à effectuer, les paramètres sont :
func = GL_NEVER, GL_LESS, GL_LEQUAL, GL_GREATER, GL_GEQUAL, GL_EQUAL, GL_NOTEQUAL, ou GL_ALWAYS
ref = valeur de référence du test
mask = masque binaire à appliquer à la valeur du stencil et à ref pour le test

void glStencilOp( GLenum fail, GLenum zfail, GLenum zpass )
qui sert à indiquer ce qui se passe suivant le résultat du test du stencil et du depth, avec :
fail indique l’opération à effectuer sur la valeur dans le stencil lorsque le stencil test a échoué. Peut être égal à GL_KEEP, GL_ZERO, GL_REPLACE, GL_INCR, GL_DECR, ou GL_INVERT
zfail indique de même l’opération à effectuer si le stencil est passé mais pas le depth
zpass indique l’opération à effectuer si le stencil et le depth est passent

Ombres et reflets

La principale application du stencil buffer, ce sont les ombres et les reflets.

Reflets
Principe

Les reflets sur un plan sont très simples : ce n’est qu’un double d’un objet, recouvert d’une surface légèrement transparente pour donner l’illusion qu’il s’agit d’un reflet.
En plus, ce qui simplifie bien les choses, il existe la fonction :

void glScalef( GLfloat x, GLfloat y, GLfloat z )

qui multiplie la matrice courante par une matrice multipliant toutes les coordonnées entrées par les valeurs spécifiées, c’est-à-dire une matrice diagonale (x, y, z, 1).
Dans notre cas, pour effectuer une symétrie par rapport au plan y = 0, il suffit d’entrer glScalef(1.0f, -1.0f, 1.0f).
Seul problème : le reflet peut sortir de la surface miroir ! C’est là que le stencil buffer intervient. La surface réfléchissante est d’abord « virtuellement » dessinée, en fait elle est dessinée normalement sauf que pour empêcher l’affichage on utilise :
void glColorMask( GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha )
avec tous les paramètres à GL_FALSE. Cela permet de changer la valeur du stencil là où la surface va être dessinée.

Les étapes du reflets sont finalement :
Limitation du reflet à la surface réfléchissante : glDisable(GL_DEPTH_TEST); glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); glEnable(GL_STENCIL_TEST); glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE); glStencilFunc(GL_ALWAYS, 1, 0xffffffff); Dessin de la surface réfléchissante glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); glEnable(GL_DEPTH_TEST);
Dessin du reflet : glStencilFunc(GL_EQUAL, 1, 0xffffffff); /* draw if ==1 */ glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); glPushMatrix(); glScalef(1.0, -1.0, 1.0); respositionnement des lumières dessin de l’objet reflété glPopMatrix(); respositionnement des lumières glDisable(GL_STENCIL_TEST);
Dessin de la surface réfléchissante : glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); Dessin de la surface réfléchissante glDisable(GL_BLEND);
Dessin de l’objet

Exemple

Le programme est cette fois complet (même le main y est).

#include <GL/glut.h>
#include <sys/timeb.h>
#include <math.h>

GLint cube = 0;

void idle (void)
{
glutPostRedisplay ();
}

void drawFloor()
{
glColor4f(0.5f, 0.5f, 0.5f, 0.75f);
glBegin(GL_QUADS);
glVertex3f(-4.0, 4.0, 0.0);
glVertex3f( 4.0, 4.0, 0.0);
glVertex3f( 4.0, -4.0, 0.0);
glVertex3f(-4.0, -4.0, 0.0);
glEnd();
}

void SetLight()
{
GLfloat ambientProperties[] = {0.7f, 0.7f, 0.7f, 1.0f};
GLfloat diffuseProperties[] = {1.0f, 1.0f, 1.0f, 1.0f};
GLfloat positionProperties[] = {5.0f, 0.0f, 0.0f, 0.0f};

glLightfv( GL_LIGHT0, GL_AMBIENT, ambientProperties);
glLightfv( GL_LIGHT0, GL_DIFFUSE, diffuseProperties);
glLightfv( GL_LIGHT0, GL_POSITION, positionProperties);

glEnable(GL_LIGHT0);
glEnable(GL_LIGHTING);
}

void display (void)
{
static timeb tt ,t;
static int total;
GLfloat Rouge[] = {0.8f, 0.0f, 0.0f, 1.0f};
GLfloat Vert[] = {0.0f, 0.8f, 0.0f, 1.0f};
GLfloat Bleu[] = {0.0f, 0.0f, 0.8f, 1.0f};
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

ftime(&t);
total += (t.time - tt.time)*1000 + (t.millitm - tt.millitm);
total %= 360*300;
tt = t;

glDisable(GL_DEPTH_TEST);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
glEnable(GL_STENCIL_TEST);
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
glStencilFunc(GL_ALWAYS, 1, 0xffffffff);
drawFloor();
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glEnable(GL_DEPTH_TEST);

glStencilFunc(GL_EQUAL, 1, 0xffffffff); /* draw if ==1 */
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glPushMatrix();
glScalef(1.0, 1.0, -1.0);
SetLight();
glTranslatef(sin(total/30.0f/180.0f*3.1415f)*5.0f, 0.0f, 5.0f);
glRotatef((total/30.0f), 0.5f, 0.3f, 0.8f);
glRotatef((total/50.0f), 0.5f, -0.3f, 0.8f);
SetLight();
glCallList(cube);
glPopMatrix();

SetLight();
glDisable(GL_STENCIL_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_LIGHTING);
drawFloor();
glEnable(GL_LIGHTING);
glDisable(GL_BLEND);

glPushMatrix();
glTranslatef(sin(total/30.0f/180.0f*3.1415f)*5.0f, 0.0f, 5.0f);
glRotatef((total/30.0f), 0.5f, 0.3f, 0.8f);
glRotatef((total/50.0f), 0.5f, -0.3f, 0.8f);
glCallList(cube);
glPopMatrix();
glutSwapBuffers();
}

void init ()
{
GLfloat Rouge[] = {0.8f, 0.0f, 0.0f, 1.0f};
GLfloat Vert[] = {0.0f, 0.8f, 0.0f, 1.0f};
GLfloat Bleu[] = {0.0f, 0.0f, 0.8f, 1.0f};

cube = glGenLists(1);
glNewList(cube, GL_COMPILE);
glBegin(GL_QUADS);
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, Rouge);
glNormal3d(0.0,0.0,1.0);
glVertex3d(-1, 1, 1);
glVertex3d(-1, -1, 1);
glVertex3d( 1, -1, 1);
glVertex3d( 1, 1, 1);
glNormal3d(0.0,0.0,-1.0);
glVertex3d( 1, 1, -1);
glVertex3d( 1, -1, -1);
glVertex3d( -1, -1, -1);
glVertex3d( -1, 1, -1);
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, Vert);
glNormal3d(1.0,0.0,0.0);
glVertex3d( 1, 1, 1);
glVertex3d( 1, -1, 1);
glVertex3d( 1, -1, -1);
glVertex3d( 1, 1, -1);
glNormal3d(-1.0,0.0,0.0);
glVertex3d( -1, 1, 1);
glVertex3d( -1, 1, -1);
glVertex3d( -1, -1, -1);
glVertex3d( -1, -1, 1);
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, Bleu);
glNormal3d(0.0,-1.0,0.0);
glVertex3d( -1, -1, 1);
glVertex3d( -1, -1, -1);
glVertex3d( 1, -1, -1);
glVertex3d( 1, -1, 1);
glNormal3d(0.0,1.0,0.0);
glVertex3d( -1, 1, 1);
glVertex3d( 1, 1, 1);
glVertex3d( 1, 1, -1);
glVertex3d( -1, 1, -1);
glEnd();
glEndList();
}

void reshape (int width, int height)
{
glViewport (0, 0, width, height);

glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
gluPerspective (45.0, ((GLfloat) width) / ((GLfloat) height),1.0,100.0);

glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();
gluLookAt (0.0,-7.0,15.0, 0.0, 0.0, 0.0,0.0,0.0, 1.0);

glEnable(GL_DEPTH_TEST);
glEnable(GL_POLYGON_SMOOTH);

SetLight();

glutPostRedisplay();
}

int main (int argc, char *argv[])
{
glutInit (&argc, argv);
glutInitDisplayMode (GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH | GLUT_STENCIL);
glutInitWindowSize (640, 480);

glutCreateWindow ("Cours09.c");
glutDisplayFunc (display);
glutReshapeFunc (reshape);
glutIdleFunc (idle);
init();
glutMainLoop ();
return 0;
}

Matrice « miroir »

L’utilisation de glScale est beaucoup moins pratique dès que la surface n’est pas un plan parallèle à un axe. Heureusment, M.J. Kilgard (de SGI / NVidia) à mis en ligne la fonction :

Ombres
Principe

Le stencil buffer joue toujours son rôle de délimitation, pour l’ombre cette fois. Reste à dessiner l’ombre elle-même, or qu’est-ce qu’une ombre ? C’est la projection de tout un objet sur un plan suivant la direction de la lumière. Cela fait (devrait faire ?) immédiatement penser à « matrice de projection ». Effectivement il suffit de multiplier la matrice ModelView par une matrice de projection pour avoir le résultat voulu.

Quelques détails tout de même :
l’ombre n’est pas le dessin de l’objet lui-même mais une surface noire (à moitié transparente). Bien que dans la réalité, ce n’est pas exactement cela : c’est tout le reste de la scène qui est plus éclairée. Cependant il est bien plus rapide d’assombrir là où est l’ombre que d’éclaircir tout le reste. S’il n’y a aucune lumière ambiante, l’ombre n’est pas transparente.
avec cette méthode, il faut faire attention à ne pas assombrir deux fois le même point : ce n’est pas parce que deux surfaces cachent la lumière que l’ombre est deux fois plus sombre ! L’astuce consiste ici à incrémenter le stencil lorsque le stencil test et le depth test passent (dès qu’un point est assombri, il ne fait plus « partie » de la surface). Cependant, il existe un cas où il faut volontairement oublier ce fait : lorsque l’objet projeté est en partie transparent. Il est alors possible d’avoir un assombrissement proportionnel au nombre de surfaces qui cachent la lumière. Sauf qu’il ne faut pas trop en abuser, le temps de calcul s’allongeant encore.
le problème de la matrice de projection : elle n’est pas dans glu ! Alors remercions une fois encore M.J. Kilgard pour sa documentation en ligne :

/* Create a matrix that will project the desired shadow. */
enum { X = 0, Y, Z, W };
void shadowMatrix(GLfloat shadowMat[4][4], GLfloat groundplane[4], GLfloat lightpos[4])
{
GLfloat dot;

dot = groundplane[X] * lightpos[X] +
groundplane[Y] * lightpos[Y] +
groundplane[Z] * lightpos[Z] +
groundplane[W] * lightpos[W];

shadowMat[0][0] = dot - lightpos[X] * groundplane[X];
shadowMat[1][0] = 0.f - lightpos[X] * groundplane[Y];
shadowMat[2][0] = 0.f - lightpos[X] * groundplane[Z];
shadowMat[3][0] = 0.f - lightpos[X] * groundplane[W];

shadowMat[X][1] = 0.f - lightpos[Y] * groundplane[X];
shadowMat[1][1] = dot - lightpos[Y] * groundplane[Y];
shadowMat[2][1] = 0.f - lightpos[Y] * groundplane[Z];
shadowMat[3][1] = 0.f - lightpos[Y] * groundplane[W];

shadowMat[X][2] = 0.f - lightpos[Z] * groundplane[X];
shadowMat[1][2] = 0.f - lightpos[Z] * groundplane[Y];
shadowMat[2][2] = dot - lightpos[Z] * groundplane[Z];
shadowMat[3][2] = 0.f - lightpos[Z] * groundplane[W];

shadowMat[X][3] = 0.f - lightpos[W] * groundplane[X];
shadowMat[1][3] = 0.f - lightpos[W] * groundplane[Y];
shadowMat[2][3] = 0.f - lightpos[W] * groundplane[Z];
shadowMat[3][3] = dot - lightpos[W] * groundplane[W];
}

avec shadowMat la matrice à construire, groundpane l’équation du plan où projeter l’ombre (de type ax + by + cz + d = 0), et lightpos comme son nom l’indique (lightpos[3] de préférence à 0).

Et pour les coefficients du plan :

/* Find the plane equation given 3 points. */
enum { A = 0, B, C, D };
void findPlane(GLfloat plane[4], GLfloat v0[3], GLfloat v1[3], GLfloat v2[3])
{
GLfloat vec0[3], vec1[3];

/* Need 2 vectors to find cross product. */
vec0[X] = v1[X] - v0[X];
vec0[Y] = v1[Y] - v0[Y];
vec0[Z] = v1[Z] - v0[Z];

vec1[X] = v2[X] - v0[X];
vec1[Y] = v2[Y] - v0[Y];
vec1[Z] = v2[Z] - v0[Z];

/* find cross product to get A, B, and C of plane equation */
plane[A] = vec0[Y] * vec1[Z] - vec0[Z] * vec1[Y];
plane[B] = -(vec0[X] * vec1[Z] - vec0[Z] * vec1[X]);
plane[C] = vec0[X] * vec1[Y] - vec0[Y] * vec1[X];

plane[D] = -(plane[A] * v0[X] + plane[B] * v0[Y] + plane[C] * v0[Z]);
}

Les étapes de l’ombre sont alors :
Limitation de l’ombre à la surface : glEnable(GL_STENCIL_TEST); glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE); glStencilFunc(GL_ALWAYS, 1, 0xffffffff); Dessin de la surface glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); glEnable(GL_DEPTH_TEST); glDisable(GL_STENCIL_TEST);
Dessin de l’objet
Calcul de la matrice de projection (avec vfloor des points de la surface) : GLfloat floorPlane[4]; GLfloat floorShadow[4][4]; findPlane(floorPlane, vfloor[1], vfloor[2], vfloor[3]); shadowMatrix(floorShadow, floorPlane, lightZeroPosition);
Changement des paramètres pour dessiner une surface noire transparente : glStencilFunc(GL_EQUAL, 1, 0xffffffff); /* draw if ==1 */ glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_LIGHTING); glDisable(GL_TEXTURE_2D); glDisable(GL_DEPTH_TEST); glColor4f(0.0, 0.0, 0.0, 0.5);
Dessin de l’ombre : glPushMatrix(); glMultMatrixf((GLfloat *) floorShadow); dessin de l’objet glPopMatrix();

Exemple

GLfloat vfloor[4][3] = {-4, 4, 0, 4, 4, 0, 4,-4, 0,-4,-4, 0};
GLfloat positionProperties[] = {0.0f, -3.0f, 5.0f, 0.0f};

void drawFloor()
{
glColor4f(0.5f, 0.5f, 0.5f, 0.75f);
glBegin(GL_QUADS);
glVertex3fv(vfloor[0]);
glVertex3fv(vfloor[1]);
glVertex3fv(vfloor[2]);
glVertex3fv(vfloor[3]);
glEnd();
}

void SetLight()
{
GLfloat ambientProperties[] = {0.7f, 0.7f, 0.7f, 1.0f};
GLfloat diffuseProperties[] = {1.0f, 1.0f, 1.0f, 1.0f};

glLightfv( GL_LIGHT0, GL_AMBIENT, ambientProperties);
glLightfv( GL_LIGHT0, GL_DIFFUSE, diffuseProperties);
glLightfv( GL_LIGHT0, GL_POSITION, positionProperties);

glEnable(GL_LIGHT0);
glEnable(GL_LIGHTING);
}

void display (void)
{
static timeb tt ,t;
static int total;
GLfloat Rouge[] = {0.8f, 0.0f, 0.0f, 1.0f};
GLfloat Vert[] = {0.0f, 0.8f, 0.0f, 1.0f};
GLfloat Bleu[] = {0.0f, 0.0f, 0.8f, 1.0f};
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

ftime(&t);
total += (t.time - tt.time)*1000 + (t.millitm - tt.millitm);
total %= 360*300;
tt = t;

glDisable(GL_DEPTH_TEST);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
glEnable(GL_STENCIL_TEST);
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
glStencilFunc(GL_ALWAYS, 1, 0xffffffff);
drawFloor();
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glEnable(GL_DEPTH_TEST);
glDisable(GL_STENCIL_TEST);

glDisable(GL_LIGHTING);
drawFloor();
glEnable(GL_LIGHTING);

glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_ALWAYS, 2, 0xffffffff);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
glPushMatrix();
glTranslatef(sin(total/30.0f/180.0f*3.1415f)*5.0f, 0.0f, 3.0f);
glRotatef((total/30.0f), 0.5f, 0.3f, 0.8f);
glRotatef((total/50.0f), 0.5f, -0.3f, 0.8f);
glCallList(cube);
glPopMatrix();

glStencilFunc(GL_EQUAL, 1, 0xffffffff); /* draw if ==1 */
glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_LIGHTING);
glDisable(GL_DEPTH_TEST);
glColor4f(0.0, 0.0, 0.0, 0.5);

static GLfloat floorPlane[4];
static GLfloat floorShadow[4][4];

findPlane(floorPlane, vfloor[1], vfloor[2], vfloor[3]);
shadowMatrix(floorShadow, floorPlane, positionProperties);

glPushMatrix();
glMultMatrixf((GLfloat *) floorShadow);
glTranslatef(sin(total/30.0f/180.0f*3.1415f)*5.0f, 0.0f, 3.0f);
glRotatef((total/30.0f), 0.5f, 0.3f, 0.8f);
glRotatef((total/50.0f), 0.5f, -0.3f, 0.8f);
glCallList(cube);
glPopMatrix();

glEnable(GL_DEPTH_TEST);
glDisable(GL_BLEND);
glEnable(GL_LIGHTING);
glDisable(GL_STENCIL_TEST);

glFlush();
glutSwapBuffers();
}

Courbes et Surfaces calculées
Présentation

Pour certaines surfaces, comme par exemple les sphères, il se pose un problème : elles ne peuvent être qu’approchées par des triangles. Il faut faire un compromis : soit avoir une sphère très belle, ce qui peut prendre plusieurs milliers de triangles, ou bien avoir peu de triangles mais une sphère pas très sphérique. De plus, pourquoi avoir des surfaces précises si elles sont éloignées ? De toute façon elles ne font que quelques pixels.
D’où l’intérêt d’avoir des surfaces calculées : la précision de la surface est ajustée suivant les besoins (distance par rapport à l’œil), et même peut être ajustée dynamiquement suivant la puissance de la machine (si le programme tourne à 60 fps, une amélioration visuelle est raisonnable).

Quadrics
Présentation

Glu intègre déjà un certain nombre de formes : sphère, cylindre et disque. Le principe est toujours le même :
Déclaration d’un « quadric » : GLUquadricObj obj = gluNewQuadric();
Appel(s) à l’une des fonctions suivantes (dessin immédiat) : void gluCylinder( GLUquadricObj *qobj, GLdouble baseRadius, GLdouble topRadius, GLdouble height, GLint slices, GLint stacks ) void gluDisk( GLUquadricObj *qobj, GLdouble innerRadius, GLdouble outerRadius, GLint slices, GLint loops ) void gluPartialDisk( GLUquadricObj *qobj, GLdouble innerRadius, GLdouble outerRadius, GLint slices, GLint loops, GLdouble startAngle, GLdouble sweepAngle ) void gluSphere( GLUquadricObj *qobj, GLdouble radius, GLint slices, GLint stacks )
Destruction avec : void gluDeleteQuadric( GLUquadricObj *state )

Suivant les paramètres du rendu, il est possible de générer :
Les normales (pour l’éclairage) : void gluQuadricNormals( GLUquadricObj *qobj, GLenum normals ) avec normals = GLU_NONE, GLU_FLAT, ou GLU_SMOOTH
Les coordonnées de texture : void gluQuadricTexture( GLUquadricObj *qobj, GLboolean textureCoords )
Le mode de dessin : void gluQuadricDrawStyle( GLUquadricObj *qobj, GLenum drawStyle ) avec drawStyle = GLU_FILL, GLU_LINE, GLU_SILHOUETTE, ou GLU_POINT

Exemple

GLfloat positionProperties[] = {5.0f, -5.0f, 5.0f, 0.0f};

void SetLight()
{
GLfloat ambientProperties[] = {0.7f, 0.7f, 0.7f, 1.0f};
GLfloat diffuseProperties[] = {1.0f, 1.0f, 1.0f, 1.0f};

glLightfv( GL_LIGHT0, GL_AMBIENT, ambientProperties);
glLightfv( GL_LIGHT0, GL_DIFFUSE, diffuseProperties);
glLightfv( GL_LIGHT0, GL_POSITION, positionProperties);

glEnable(GL_LIGHT0);
glEnable(GL_LIGHTING);
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, ambientProperties);
}

void display (void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
GLUquadricObj *obj = gluNewQuadric();
gluQuadricNormals(obj, GL_SMOOTH);
gluQuadricDrawStyle(obj, GLU_FILL);
gluSphere(obj, 1.0, 20, 20);
gluDeleteQuadric(obj);
glFlush();
glutSwapBuffers();
}

Bézier

OpenGL intègre (ce n’est pas glu cette fois) les courbes et les surfaces de Bézier. Cependant les cartes grand public n’accélèrent pas leur calcul. Pour l’instant…
Le principe d’une courbe de Bézier est le suivant :
Soit n point de contrôle, la courbe part du premier et arrive au dernier
La courbe est calculée par récurrence, en rajoutant entre deux points de contrôle leur milieu jusqu’à la résolution de l’affichage. Seul les milieux du dernier niveau de récurrence sont affichés (en fait dès qu’un point, hormis les extrémités la courbe, a servi pour calculer un milieu, il est éliminé). Un petit dessin pour essayer de mieux comprendre :

Le principe est le même pour les surfaces.
Courbe
Fonctions OpenGL

La première chose à faire est de rentrer les points de contrôle :

void glMap1f( GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points )

Target Indique quel type de coordonnées il faut générer. Les choix sont : GL_MAP1_VERTEX_3, GL_MAP1_VERTEX_4, GL_MAP1_INDEX, GL_MAP1_COLOR_4, GL_MAP1_NORMAL, GL_MAP1_TEXTURE_COORD_1, GL_MAP1_TEXTURE_COORD_2, GL_MAP1_TEXTURE_COORD_3, ou GL_MAP1_TEXTURE_COORD_4
Pour une courbe de Bézier, il faut choisir GL_MAP1_VERTEX_3. Mais il est aussi possible d’utiliser GL_MAP1_COLOR_4 pour créer un dégradé de couleurs : quelle différence entre générer des coordonnées (x, y,z) et (R, G, B) ? u1, u2 Une courbe est ensuite repérée par un « axe courbe ». Par la suite, la courbe débutera en (u1) et se terminera en (u2) Stride Indique le nombre de flottants qui sépare un point de contrôle dans points du suivant. Le seul but de cette valeur est de pouvoir utiliser des tableaux de structure avec d’autres données que les points. Order Nombre de points dans le tableau Points Pointeur vers les points de contrôle
Il faut appeler glEnable avec le même target pour activer la courbe.

Ensuite la fonction :

void glEvalCoord1f( GLfloat u)

fait l’équivalent d’un glVertex(coordonnées du point repéré par u). Par exemple glEvalCoord1f(u1) à le même effet que glVertex(premier point de contrôle), et de même glEvalCoord1f(u2) à le même effet que glVertex(dernier point de contrôle). La valeur de u n’est pas limitée à [u1..u2]. Si elle sort de cette intervalle, cela prolonge simplement la courbe.
La fonction doit être appelée à l’interieur d’un glBegin / glEnd.

Exemple

void display (void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
static GLfloat ctrlpoints[4][3] = {
{-4.0, -4.0, -15.0}, {-2.0, 4.0, -15.0},
{ 2.0, -4.0, -15.0}, { 4.0, 4.0, -15.0}};
glColor3f(1.0f,1.0f,1.0f);
glMap1f(GL_MAP1_VERTEX_3, 0.0, 1.0, 3, 4, &ctrlpoints[0][0]);
glEnable(GL_MAP1_VERTEX_3);
glBegin(GL_LINE_STRIP);
for (GLfloat i = 0.0; i <= 1.0; i+=0.05f)
glEvalCoord1f(i);
glEnd();
glDisable(GL_MAP1_VERTEX_3);

glPointSize(5.0);
glColor3f(1.0, 1.0, 0.0);
glBegin(GL_POINTS);
for (int j = 0; j < 4; j++)
glVertex3fv(&ctrlpoints[j][0]);
glEnd();
glPopMatrix();

glFlush();
glutSwapBuffers();
}

void reshape (int width, int height)
{
glViewport (0, 0, width, height);

glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
gluPerspective (45.0, ((GLfloat) width) / ((GLfloat) height),1.0,100.0);

glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();
gluLookAt(0, 0, 0, 0, 0, -1, 0, 1, 0);

glEnable(GL_DEPTH_TEST);
glEnable(GL_POLYGON_SMOOTH);
}

Surface
Fonctions OpenGL

Les fonctions sont les mêmes avec des « 2 » au lieu de « 1 », et le tableau des points de contrôle est à 2 dimensions :

void glMap2f( GLenum target, Glfloat u1, Glfloat u2, GLint ustride, GLint uorder, Glfloat v1, Glfloat v2, GLint vstride, GLint vorder, const GLfloat *points )
void glEvalCoord2f( GLfloat u, GLfloat v)

Il y cependant une fonction supplémentaire bien utile :

void glEvalMesh2( GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2 )

qui est purement et simplement :

glBegin(mode);
for (j = j1; j <= j2; j += 1)
for (i = i1; i <= i2; i += 1)
glEvalCoord2(i·du + u1, j·dv + v1);
glEnd();

avec du = (u2 - u1) / i et dv = (v2 - v1) / j
(mode est au choix : GL_QUAD_STRIP, GL_LINE_STRIP, GL_POINTS).

Exemple

void display (void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
static GLfloat ctrlpoints[4][4][3] = {
{{-1.5, -1.5, 4.0}, {-0.5, -1.5, 2.0},
{ 0.5, -1.5, -1.0}, { 1.5, -1.5, 2.0}},
{{-1.5, -0.5, 1.0}, {-0.5, -0.5, 3.0},
{ 0.5, -0.5, 0.0}, { 1.5, -0.5, -1.0}},
{{-1.5, 0.5, 4.0}, {-0.5, 0.5, 0.0},
{ 0.5, 0.5, 3.0}, { 1.5, 0.5, 4.0}},
{{-1.5, 1.5, -2.0}, {-0.5, 1.5, -2.0},
{ 0.5, 1.5, 0.0}, { 1.5, 1.5, -1.0}}
};
glEnable(GL_LINE_SMOOTH);
glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 12, 4, &ctrlpoints[0][0][0]);
glEnable(GL_MAP2_VERTEX_3);
static GLfloat tt= 0;
if ((tt += 0.05f) > 360) tt -= 360.0f;
glTranslatef(0.0f, 0.0f, -10.0f);
glRotatef(tt, 0.7f, 0.0f, 1.0f);
glColor3f(1.0, 1.0, 1.0);
glMapGrid2f(9, 0.0, 1.0, 9, 0.0, 1.0);
glEvalMesh2(GL_LINE, 0, 9, 0, 9);
glDisable(GL_MAP2_VERTEX_3);
glPopMatrix();
glFlush();
glutSwapBuffers();
}

void reshape (int width, int height)
{
glViewport (0, 0, width, height);

glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
gluPerspective (45.0, ((GLfloat) width) / ((GLfloat) height),1.0,100.0);

glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();
gluLookAt(0, 0, 0,
0, 0, -0.5f,
0, 1, 0);

glEnable(GL_DEPTH_TEST);
glEnable(GL_POLYGON_SMOOTH);
}

Ce qui n’a pas de place ailleurs
Dessiner deux fois au même endroit

Il se peut qu’il faille dessiner deux fois au même endroit, pour mettre un logo sur un rectangle par exemple. Le depth-buffer pose alors un problème : il n’est pas précis, ou en tout cas pas suffisament précis pour être sûr qu’ancun point du logo ne va être éliminé.
La solution la plus simple : désactiver le depth-test. Mais ce n’est à utiliser que lorsque l’on est sûr qu’il n’y a rien de plus proche de la caméra.
Une autre solution, parfaitement sûre mais plus compliquée, c’est d’utiliser le stencil buffer : dessiner d’abord le logo et changer la valeur du stencil là où le logo est dessiné. Dessiner ensuite le rectangle sur lequel est placé le logo, mais seulement là où le stencil n’a pas bougé.
Dernière solution, la fonction :

void glPolygonOffset(Glfloat factor, Glfloat units)

avec factor qui permet de « lisser » la profondeur (limite la dérivée du depth, sans intérêt ici), et units le nombre « d’unités » de décalage à ajouter au point. L’unité est dépendante de l’implémentation GL, et est spécifiée comme étant « le plus petit décalage tel que deux points ayant la même profonfeur soit quand même disctincts ». Dans le cas qui nous intéresse, cela donne :
glPolygonOffset(0, 1);

Il faut appeler glEnable avec GL_POLYGON_OFFSET_FILL pour activer le décalage des points.
Cette méthode fonctionne assez bien, mais la précision n’atteint pas celle du stencil : si le nombre de calculs (nombre de matrcies dans modelview) est trop différent pour le logo et la surface, il se peut très bien (pour cause de calcul en flottant et d’imprécision du depth) que cette technique ne marche pas. Elle est par contre très fiable si les matrices utilisées sont les mêmes pour le logo et la surface.

Clipping

Il est possible de rajouter des plans de clipping, pour exclure certaines parties du monde dessiné. Il en existe toujours au moins deux : ceux qui limitent l’intervalle de z (automatiquement générés lors de l’appel de gluPerspective).

Un plan quelconque peut être ajouté avec :

void glClipPlane( GLenum plane, const GLdouble *equation )

plane étant GL_CLIP_PLANEi (=GL_CLIP_PLANE0 + i), la valeur max étant GL_MAX_CLIP_PLANES. Equation est un tableau de quatre flottants donnant l’équation du plan (ax + by + cz + d = 0). Le nombre minimal de plans possible est 6 (pour définir un cube).
Pour l’activer : glEnable(GL_CLIP_PLANEi).

La règle pour savoir si un vertex est coupé ou pas est : « If the dot product of the eye coordinates of a vertex with the stored plane equation components is positive or zero, the vertex is in with respect to that clipping plane. Otherwise, it is out. »

Multitexturing

Le but de cette partie est de montrer les possibilités d’OpenGL en combinant plusieurs des fonctions vues jusqu’à présent.
Le « multitexturing » consiste à faire plusieurs rendus d’une même surface en changeant certaines options, pour obtenir des effets lumineux. L’exemple donné ici consiste à dessiner deux fois le même objet, une fois avec une texture non éclairée, puis une deuxième fois pour ajouter un rendu sans texture mais éclairé.

En OpenGL, cela donne à peu près :

glEnable(GL_BLEND);
glDepthFunc(GL_LEQUAL);
glDepthMask(GL_TRUE);
glDisable(GL_LIGHTING);
glDisable(GL_DITHER);
glShadeModel(GL_FLAT);
glEnable(GL_TEXTURE_2D);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Draw();
glEnable(GL_LIGHTING);
glDisable(GL_TEXTURE_2D);
glDepthFunc(GL_EQUAL);
glDepthMask(GL_FALSE);
glEnable(GL_DITHER);
glBlendFunc(GL_ONE, GL_ONE);
Draw();
²
- PAGE 6 -


OpenGL sample errors assembling cube.asm

WinAsm Studio, The Assembly IDE - Free Downloads, Source Code
Sponsors
Articles
Programming Quick Start
32-bit Assembler is Easy
Porting Iczelion tutorials
What is FASM
Hard Drive Recovery
Wiring your own LAN
Personal menu
Welcome Guest
User:
Pass:
Register!
Resend Validation Email
Forum

OpenGL sample errors assembling cube.asm, when I try to assemble an OpenGL sample found on the internet, I get these errors, how can I get it to assemble?

Track this topic | Email this topic | Print this topic
Cra8zyNbunny
Quote Post

New Member
*

Group: Members
Posts: 3
Member No.: 50203
Joined: 13-March 12
QUOTE
C:\masm32\examples\exampl04\OpenGLExample.asm(82) : error A2008: syntax error :
HGLRC
C:\masm32\examples\exampl04\OpenGLExample.asm(314) : error A2006: undefined symb
ol : hRC
C:\masm32\examples\exampl04\OpenGLExample.asm(314) : error A2114: INVOKE argumen
t type mismatch : argument : 2
C:\masm32\examples\exampl04\OpenGLExample.asm(331) : error A2006: undefined symb
ol : hRC
C:\masm32\examples\exampl04\OpenGLExample.asm(331) : error A2114: INVOKE argumen
t type mismatch : argument : 1
C:\masm32\examples\exampl04\OpenGLExample.asm(313) : error A2006: undefined symb
ol : hRC

_
Assembly Error
Press any key to continue . . .

the source code is attched there on the bottom so you can assemble it.. I think I have all the default masm32 inc and lib files. maybe it is in the source code.I suck at x86, because I need more time for my family and friends.

Code:
CODE
.486P
.Model Flat,StdCall ; 32 bit memory model
Option Scoped ; local labels are enabled, global labels inside
; PROC should be defined with double colons (LABEL::)
Option CaseMap:None ; case sensitive

Include C:\masm32\include\windows.inc
Include C:\masm32\include\user32.inc
Include C:\masm32\include\kernel32.inc
Include C:\masm32\include\gdi32.inc

Include C:\masm32\include\glu32.inc
Include C:\masm32\include\opengl32.inc

IncludeLib C:\masm32\lib\user32.lib
IncludeLib C:\masm32\lib\kernel32.lib
IncludeLib C:\masm32\lib\opengl32.lib
IncludeLib C:\masm32\lib\glu32.lib
IncludeLib C:\masm32\lib\gdi32.lib

glRed Macro
invoke glColor3f, val1_0d, val0_0d, val0_0d;
EndM

glGreen Macro
invoke glColor3f, val0_0d, val1_0d, val0_0d;
EndM

glBlue Macro
invoke glColor3f, val0_0d, val0_0d, val1_0d;
EndM

glWhite Macro
invoke glColor3f, val1_0d, val1_0d, val1_0d;
EndM

glBlack Macro
invoke glColor3f, val0_0d, val0_0d, val0_0d;
EndM

glGrey Macro
invoke glColor3f, val0_5d, val0_5d, val0_5d;
EndM

; PROTOS ================================================
WinMain Proto :DWORD,:DWORD,:DWORD,:DWORD
WndProc Proto :DWORD,:DWORD,:DWORD,:DWORD
EnableOpenGL Proto :DWORD
DisableOpenGL Proto :DWORD
ResizeGLScene Proto :DWORD,:DWORD

; Read-only data ========================================
.Const

ClassName BYTE "OGLMainWinClass", 0
AppName BYTE "Asm + OpenGL Application", 0

val45_0q QWORD 45.0
val1_0q QWORD 1.0
val0_1q QWORD 0.1
val100_0q QWORD 100.0
val0_0d DWORD 0.0
val0_5d DWORD 0.5
val_8_0d DWORD -8.0
val1_0d DWORD 1.0
val_1_0d DWORD -1.0

; Initialized data ======================================
.Data
widthwnd DWORD 640
heightwnd DWORD 480
angle REAL4 0.0
delta_angle REAL4 0.1
vangle REAL4 30.0

; Non-initialized data ==================================
.data?

hInstance HINSTANCE ?
hDC HDC ?
hRC HGLRC ?

.Code
Start:

invoke GetModuleHandle, NULL
mov hInstance, eax
invoke WinMain, hInstance, NULL, NULL, SW_SHOWDEFAULT

push 0h
call ExitProcess

;========================================================
WinMain Proc hInst:HINSTANCE, hPrevInst:HINSTANCE, CmdLine:LPSTR, CmdShow:DWORD

LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
LOCAL bQuit:BOOL

mov bQuit, FALSE

mov wc.cbSize, sizeof WNDCLASSEX
mov wc.style, CS_OWNDC
mov wc.lpfnWndProc, offset WndProc
mov wc.cbClsExtra, NULL
mov wc.cbWndExtra, NULL
push hInstance
pop wc.hInstance

invoke GetStockObject, BLACK_BRUSH
mov wc.hbrBackground, eax

mov wc.lpszMenuName, NULL
mov wc.lpszClassName, offset ClassName

invoke LoadIcon, NULL, IDI_WINLOGO
mov wc.hIcon, eax
mov wc.hIconSm, eax

invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax

invoke RegisterClassEx, addr wc
invoke CreateWindowEx,NULL, addr ClassName, addr AppName,\
WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT, CW_USEDEFAULT,\
widthwnd, heightwnd, NULL, NULL,\
hInst, NULL
mov hwnd,eax

invoke ShowWindow, eax, SW_SHOWNORMAL
invoke UpdateWindow, hwnd
invoke EnableOpenGL, hwnd

.while !( bQuit )

invoke PeekMessage, addr msg, NULL, 0, 0, PM_REMOVE
.if ( eax )

.if ( msg.message == WM_QUIT )

mov bQuit, TRUE

.else

invoke TranslateMessage, addr msg
invoke DispatchMessage, addr msg

.endif

.else

invoke glClear, GL_COLOR_BUFFER_BIT OR GL_DEPTH_BUFFER_BIT;Clear buffers
invoke glLoadIdentity ;//Reset The Current Modelview Matrix
invoke glTranslatef, val0_0d, val0_0d, val_8_0d
invoke glRotatef, DWORD PTR vangle, val1_0d, val0_0d, val0_0d
invoke glRotatef, DWORD PTR angle, val0_0d, val1_0d, val0_0d

invoke glBegin, GL_QUADS; //Draw A Quad

glRed
invoke glVertex3f, val1_0d, val1_0d, val_1_0d; // Top Right Of The Quad (Top)
glGreen
invoke glVertex3f, val_1_0d, val1_0d, val_1_0d; // Top Left Of The Quad (Top)
glBlue
invoke glVertex3f, val_1_0d, val1_0d, val1_0d; // Bottom Left Of The Quad (Top)
glWhite
invoke glVertex3f, val1_0d, val1_0d, val1_0d; // Bottom Right Of The Quad (Top)

glBlack
invoke glVertex3f, val1_0d, val_1_0d, val1_0d; // Top Right Of The Quad (Bottom)
glGrey
invoke glVertex3f, val_1_0d, val_1_0d, val1_0d; // Top Left Of The Quad (Bottom)
glRed
invoke glVertex3f, val_1_0d, val_1_0d, val_1_0d; // Bottom Left Of The Quad (Bottom)
glGreen
invoke glVertex3f, val1_0d, val_1_0d, val_1_0d; // Bottom Right Of The Quad (Bottom)

glBlue
invoke glVertex3f, val1_0d, val1_0d, val1_0d; // Top Right Of The Quad (Front)
glWhite
invoke glVertex3f, val_1_0d, val1_0d, val1_0d; // Top Left Of The Quad (Front)
glBlack
invoke glVertex3f, val_1_0d, val_1_0d, val1_0d; // Bottom Left Of The Quad (Front)
glGrey
invoke glVertex3f, val1_0d, val_1_0d, val1_0d; // Bottom Right Of The Quad (Front)

glRed
invoke glVertex3f, val1_0d, val_1_0d, val_1_0d; // Top Right Of The Quad (Back)
glGreen
invoke glVertex3f, val_1_0d, val_1_0d, val_1_0d; // Top Left Of The Quad (Back)
glBlue
invoke glVertex3f, val_1_0d, val1_0d, val_1_0d; // Bottom Left Of The Quad (Back)
glWhite
invoke glVertex3f, val1_0d, val1_0d, val_1_0d; // Bottom Right Of The Quad (Back)

glBlack
invoke glVertex3f, val_1_0d, val1_0d, val1_0d; // Top Right Of The Quad (Left)
glGrey
invoke glVertex3f, val_1_0d, val1_0d, val_1_0d; // Top Left Of The Quad (Left)
glRed
invoke glVertex3f, val_1_0d, val_1_0d, val_1_0d; // Bottom Left Of The Quad (Left)
glGreen
invoke glVertex3f, val_1_0d, val_1_0d, val1_0d; // Bottom Right Of The Quad (Left)

glBlue
invoke glVertex3f, val1_0d, val1_0d, val_1_0d; // Top Right Of The Quad (Right)
glWhite
invoke glVertex3f, val1_0d, val1_0d, val1_0d; // Top Left Of The Quad (Right)
glBlack
invoke glVertex3f, val1_0d, val_1_0d, val1_0d; // Bottom Left Of The Quad (Right)
glGrey
invoke glVertex3f, val1_0d, val_1_0d, val_1_0d; // Bottom Right Of The Quad (Right)

invoke glEnd

invoke SwapBuffers,hDC

fld angle
fadd delta_angle
fstp angle

.endif

.endw

invoke DisableOpenGL, hwnd

mov eax, msg.wParam
ret

WinMain EndP

;========================================================
WndProc Proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM

LOCAL Rect:RECT

.if ( uMsg == WM_DESTROY )

quit_label:
invoke PostQuitMessage, NULL
mov eax, 0
jmp ret_label

.elseif ( uMsg == WM_CREATE )

.elseif ( uMsg == WM_SIZE )

mov eax,lParam;LOWORD lParam
and eax,0FFFFh;LOWORD lParam
mov ebx,lParam;HIWORD lParam
shr ebx,16;HIWORD lParam
and ebx,0FFFFh;HIWORD lParam
invoke ResizeGLScene, eax, ebx
mov eax, 0
jmp ret_label

.elseif ( uMsg == WM_KEYDOWN )

.if ( wParam == VK_ESCAPE)

jmp quit_label

.endif

.else

invoke DefWindowProc, hWnd, uMsg, wParam, lParam
jmp ret_label

.endif

ret_label:
ret

WndProc EndP

;========================================================
EnableOpenGL Proc hWnd:HWND

LOCAL iFormat:dword
LOCAL pfd:PIXELFORMATDESCRIPTOR

; get the device context (DC)
invoke GetDC, hWnd
mov hDC, eax

; set the pixel format for the DC
lea edi, pfd
xor eax, eax
mov ecx, sizeof pfd / sizeof DWORD
rep stosd ; zero pfd structure

mov pfd.nSize, sizeof pfd
mov pfd.nVersion, 1
mov pfd.dwFlags, PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER
mov pfd.iPixelType, PFD_TYPE_RGBA
mov pfd.cColorBits, 16
mov pfd.cDepthBits, 16
mov pfd.iLayerType, PFD_MAIN_PLANE
invoke ChoosePixelFormat, hDC, addr pfd
mov iFormat, eax
invoke SetPixelFormat, hDC, iFormat, addr pfd

; create and enable the render context (RC)
invoke wglCreateContext, hDC
mov hRC, eax
invoke wglMakeCurrent, hDC, hRC
invoke ResizeGLScene, widthwnd, heightwnd
;Init OpenGL
invoke glShadeModel,GL_SMOOTH;// Enable Smooth Shading
invoke glClearColor,DWORD PTR val0_0d,DWORD PTR val0_0d,DWORD PTR val0_0d, DWORD PTR val0_5d ;// Black Background
invoke glClearDepth,DWORD PTR val1_0q[0],DWORD PTR val1_0q[4];// Depth Buffer Setup
invoke glEnable,GL_DEPTH_TEST;// Enables Depth Testing
invoke glDepthFunc,GL_LEQUAL;// The Type Of Depth Testing To Do
invoke glHint,GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST;//Init OpenGL
ret

EnableOpenGL EndP

;========================================================
DisableOpenGL Proc hWnd:HWND

invoke wglMakeCurrent, NULL, NULL
invoke wglDeleteContext, hRC
invoke ReleaseDC, hWnd, hDC
ret

DisableOpenGL EndP

;========================================================
ResizeGLScene Proc nwidth:DWORD,nheight:DWORD

LOCAL ratio:REAL8
invoke glViewport,0,0,nwidth,nheight; // Reset The Current Viewport

invoke glMatrixMode,GL_PROJECTION; // Select The Projection Matrix
invoke glLoadIdentity; // Reset The Projection Matrix

;// Calculate The Aspect Ratio Of The Window
fild nwidth
fild nheight
fdivp st(1), st(0)
fstp ratio

invoke gluPerspective,DWORD PTR val45_0q,DWORD PTR val45_0q[4],\
DWORD PTR ratio,DWORD PTR ratio[4],DWORD PTR val0_1q,DWORD PTR val0_1q[4],\
DWORD PTR val100_0q,DWORD PTR val100_0q[4]

invoke glMatrixMode,GL_MODELVIEW; // Select The Modelview Matrix
invoke glLoadIdentity
ret

ResizeGLScene EndP
End Start

Sponsored Links
shoorick
Quote Post

Extremely Active Member
* * * * * *

Group: Admins
Posts: 2319
Member No.: 160
Joined: 22-June 04
at first look for the first error:
CODE
C:\masm32\examples\exampl04\OpenGLExample.asm(82) : error A2008: syntax error :
HGLRC

seems as HGLRC is structure which is not defined. search and include its definition, or type it by yourself using opengl docs. most other errors looks like appeared of the same reason.

all asm packages and examples for windows are made by volunteers thus always are incomplete.
Cra8zyNbunny | Quote Post
New Member * Group: Members Posts: 3 Member No.: 50203 Joined: 13-March 12 | Thanks Alot shoorick may Jesus Christ bless every assembly language coder and guide them on this journey of life, may The Lord refresh you all bless you, give you peace wisdom, understanding, patience, long life, health, joy, love, care and abudancy of grace. Every one should appreciate assembly c0d3rs all around the globes ardous persistance and diligence. You all have touched my heart may the fresh life giving breath from eternity resurrect you all into the above and beyond.:)Amen!
0 User(s) are reading this topic (0 Guests and 0 Anonymous Users)
0 Members:
« Next Oldest | Newbies | Next Newest »

Topic Options Reply to this topic Start new topic
> Close Topic Options
Track this topic
Receive email notification when a reply has been made to this topic and you are not active on the board.

Subscribe to this forum
Receive email notification when a new topic is posted in this forum and you are not active on the board.

Download / Print this Topic
Download this topic in different formats or view a printer friendly version.

Sponsors
Computer Science

Internet
C/C++
Hardware & PC maintenance

HiEditor

General Discussions
Suggestions/Bug Reports 		WinAsm Studio

General Discussions
Suggestions/Bug Reports
WinAsm Studio FAQ
Multilingual User Interface
Add-Ins 		Assembly Programming

Main
Newbies
Projects
Custom Controls
Snippets 		Announcements & Rules

Announcements

General

Online Degrees - Distance Learning
The Heap
Russian
Google


Apprendre OpenGL

Vu dans ... https://opengl.developpez.com/tutoriels/apprendre-opengl/?page=creer-une-fenetre

4. Créer une fenêtre

La première chose à faire pour créer une application avec de superbes graphismes est de créer un contexte OpenGL et la fenêtre de l’application pour l’affichage. Cependant, ces opérations dépendent du système sur lequel vous travaillez et par conséquent, OpenGL rend ces opérations abstraites. Cela signifie que nous devons créer une fenêtre, définir un contexte et gérer les entrées utilisateur nous-mêmes.

Heureusement, il existe quelques bibliothèques qui permettent de faire cela, certaines étant spécifiques à OpenGL. Ces bibliothèques vont nous économiser ce travail qui dépend du système d’exploitation utilisé. Les plus connues de ces bibliothèques sont GLUT, SDL, SFML et GLFW. Pour nos tutoriels, nous utiliserons GLFW.

4-1. GLFW

GLFW est une bibliothèque, écrite en C, spécifiquement destinée à OpenGL, fournissant les outils de base requis pour afficher des choses sur l’écran. Cela va nous permettre de créer un contexte OpenGL, définir les paramètres de la fenêtre et gérer les entrées utilisateur.

Le sujet de ce tutoriel et du suivant est de configurer GLFW, de le faire fonctionner, s’assurant qu’il crée un contexte OpenGL correct et qu’il affiche une fenêtre pour les rendus. Ce chapitre est une approche pas à pas pour obtenir, construire et lier la bibliothèque GLFW. Dans ce tutoriel, nous utilisons l’IDE Microsoft Visual Studio 2015 (la démarche est la même pour les versions plus récentes de l’IDE). Si vous n’utilisez pas Visual Studio (ou une version plus ancienne), pas de souci, la démarche est semblable sur la plupart des autres outils.

4-2. Compiler GLFW

Vous pouvez télécharger GLFW sur le site officiel. GLFW propose déjà des binaires précompilés et des fichiers d’en-tête pour Visual Studio 2013/2015, mais par souci de complétude, nous compilerons nous-mêmes le code source. Téléchargez le « Source package ».

Si vous utilisez les binaires précompilés, assurez-vous de télécharger les versions 32 bits (à moins de savoir exactement ce que vous faites). Les versions 64 bits ont causé pas mal d’erreurs curieuses à la plupart des lecteurs.

Une fois téléchargé, extrayez les fichiers et ouvrez-les. Nous nous intéresserons seulement à ces deux choses :

  • la bibliothèque obtenue après compilation ;
  • le dossier include.

Compiler la bibliothèque à partir des sources nous garantit que celle-ci sera parfaitement adaptée à notre CPU et OS, ce que les bibliothèques précompilées ne fournissent pas toujours. Le problème quand on fournit le code source, est que les utilisateurs n’ont pas tous le même IDE ce qui implique qu’ils doivent chacun construire leur projet avec les fichiers .cpp, .h, .hpp, ce qui est pénible. Pour cette raison, il existe un outil appelé CMake.

4-2-1. CMake

CMake est un outil pour générer les fichiers de projet selon le choix de l’utilisateur (par exemple Visual Studio, Code::Blocks, Eclipse) à partir d’un ensemble de fichiers source et utilisant des scripts CMake prédéfinis. Cela nous permet de générer un fichier projet Visual Studio 2012, à partir des sources de GLFW, que nous pourrons ensuite compiler pour créer la bibliothèque. Nous devons donc charger et installer CMake que l’on peut trouver ici. Pour ma part, j’ai utilisé l’installateur Win32.

Une fois CMake installé, vous pouvez le lancer à partir d’une ligne de commande ou au moyen du GUI (Graphical User Interface). Pour simplifier, nous utiliserons le GUI. CMake demande de préciser un répertoire pour le code source et un répertoire de destination pour les binaires. Notre répertoire source sera la racine du package GLFW et nous créons un nouveau répertoire nommé build avant de le sélectionner pour CMake.

Configuration de CMake

Une fois ces deux répertoires définis, cliquez sur le bouton Configure pour que CMake lise le code source et les paramètres. Nous choisissons ensuite le générateur pour ce projet, ici l’option Visual Studio 14 (Visual Studio 2015 s’appelle aussi Visual Studio 14). CMake va ensuite afficher les différentes options de compilation, nous gardons les valeurs par défaut et cliquons à nouveau sur Configure. Puis nous cliquons sur Generate et les fichiers produits seront placés dans le répertoire build.

4-2-2. Compilation

On trouvera dans le répertoire build un fichier nommé GLFW.sln, ouvrons-le avec Visual Studio 2015. Puisque CMake a généré un fichier projet qui contient déjà la bonne configuration, il nous suffit de cliquer sur Build Solution et la bibliothèque compilée se trouvera dans src/Debug sous le nom glfw3.lib (notons que nous utilisons la version 3).

Cette bibliothèque étant générée, nous devons nous assurer que l’IDE sait où se trouvent cette bibliothèque et les fichiers à inclure. On peut faire cela de deux façons :

  1. Trouver où est placé le répertoire include de notre compilateur et y copier le contenu du répertoire include de GLFW et aussi ajouter glfw3.lib dans le répertoire lib de l’IDE. Ça marche mais ce n’est pas la bonne méthode. Si vous réinstallez votre IDE, vous perdrez la trace de vos fichiers GLFW.
  2. La bonne méthode consiste à créer un nouvel ensemble de répertoires à l’endroit de votre choix, contenant les fichiers d’en-tête et bibliothèque de GLFW et dire à votre IDE où ils se trouvent. Pour ma part, j’ai créé un répertoire qui contient un sous-répertoire lib et un autre de nom include, où je place mes fichiers GLFW, ces répertoires servant pour chaque projet OpenGL. Bien sûr, pour chaque nouveau projet, il faut indiquer où se trouvent ces fichiers.

Cette étape accomplie, nous pouvons commencer à créer notre premier projet avec GLFW !

4-3. Notre premier projet

Créons un nouveau projet avec Visual Studio. Choisir Visual C++ si plusieurs options se présentent ainsi qu’un projet vide (Empty Project) (donner aussi un nom correct à votre projet). Nous avons alors un espace de travail pour créer notre première application OpenGL.

4-4. Édition des liens (linking)

Pour utiliser GLFW, il faut lier la bibliothèque GLFW à notre projet. Il faut donc ajouter glfw3.lib dans la configuration de l’éditeur de liens du projet (linker settings), mais l’IDE ne sait pas où trouver glfw3.lib puisque nous l’avons placé dans le répertoire de notre choix. Il faut donc ajouter ce répertoire aux répertoires utilisés par Visual Studio pour trouver les bibliothèques.

Il faut modifier les propriétés du projet et trouver les répertoires utilisés avec VC++ comme indiqué ci-dessous :

Configuration des répertoire pour Visual Studio VC++

Dès lors, vous pouvez ajouter vos propres répertoires pour indiquer où trouver les fichiers. Cela peut être fait manuellement en insérant du texte ou bien en cliquant sur <Edit..> à la bonne ligne (lib ou include) :

Configuration des répertoires des includes de Visual Studio

Là, vous pouvez ajouter autant de répertoires que vous voulez et l’IDE cherchera vos fichiers aussi dans ces répertoires. Par exemple, il pourra trouver les fichiers d’en-tête nommés <GLFW/..>. C’est la même chose pour les bibliothèques.

Nous devons maintenant préciser quelles bibliothèques seront nécessaires pour générer le projet dans le menu Linker → input :

Configuration de l'éditeur de liens de Visual Studio

On spécifie le nom de la bibliothèque glfw3.lib que l’on ajoute aux Additional Dependencies, ce qui a pour effet de lier la bibliothèque GLFW à notre projet. On doit aussi ajouter la bibliothèque OpenGL, mais cela dépend des systèmes d’exploitation.

4-4-1. Bibliothèque OpenGL sur Windows

La bibliothèque opengl32.lib est installée avec Visual Studio, il nous suffit d’ajouter opengl32.lib dans les paramètres de l’éditeur de liens.

4-4-2. Bibliothèque OpenGL sur Linux

Sur Linux, il faut utiliser la bibliothèque libGL.so en ajoutant -lGL dans les paramètres de l’éditeur de liens. Si vous ne trouvez pas ce fichier (dans /usr/lib ou dans les sous-répertoires comme x86_64-linux-gnu), vous devrez probablement installer un de ces paquets : Mesa, NVidia ou AMD, mais je ne rentrerai pas dans ces détails (n’étant pas expert sur Linux).

Pour les utilisateurs de Linux, cette ligne de commande pourra vous aider à compiler le projet : -lglfw3 -lGL -lX11 -lpthread -lXrandr -lXi -ldl.

Les bibliothèques ayant été bien configurées, on inclut le fichier d’en-tête de GLFW comme suit :

Sélectionnez
#include <GLFW\glfw3.h>

Voilà pour la configuration de GLFW.

4-5. GLAD

Nous ne sommes pas encore arrivés, il reste une autre chose à faire. Puisqu’OpenGL est une spécification, il appartient au fabricant du pilote de la carte graphique d’implémenter cette API. Mais il existe beaucoup de versions différentes de pilotes OpenGL, l’emplacement de la plupart des fonctions n’est pas connu lors de la compilation et doit être retrouvé à l’exécution. Cela reste à la charge du développeur et cela dépend du système d’exploitation utilisé. Sur Windows cela ressemble à ça :

Sélectionnez
// Prototype de la fonction
typedef void (*GL_GENBUFFERS) (GLsizei, Gluint*);

// recherche de la fonction et mémorisation de son adresse
GL_GENBUFFERS glGenBuffers  = (GL_GENBUFFERS)wglGetProcAddress("glGenBuffers");

// la fonction peut ensuite être appelée normalement
unsigned int buffer;
glGenBuffers(1, &buffer);

Ce code est complexe et il serait pesant de l’écrire pour chaque fonction. Fort heureusement, une bibliothèque existe pour cela : GLAD, qui est bien connue et bien maintenue.

4-5-1. Installer GLAD

GLAD est une bibliothèque open source, qui s’installe un peu différemment de la plupart des bibliothèques. GLAD utilise un service web où nous pouvons préciser quelle est la version d’OpenGL utilisée et qui charge toutes les fonctions OpenGL correspondantes.

Rendez-vous sur le service web, choisissez le langage C++ et dans la section API, sélectionnez au minimum la version 3.3 (version utilisée dans les tutoriels, mais une version supérieure fonctionnera aussi). Assurez-vous de choisir le core-profile et que l’option Generate a loader est cochée. Cliquez sur Generate pour produire les fichiers de la bibliothèque.

GLAD devrait vous fournir un fichier zip contenant deux répertoires include et un fichier glad.c. Copiez ces deux répertoires include (glad et KHR) dans votre répertoire include (ou ajoutez un pointeur vers ces emplacements) et ajoutez le fichier glad.c à votre projet.

Il faut maintenant ajouter une directive include à votre fichier source :

Sélectionnez
#include <glad/glad.h>

Lancez la compilation. Elle devrait se produire sans erreur. Nous sommes prêts pour le prochain tutoriel où nous verrons comment utiliser GLAD et GLFW pour configurer un contexte OpenGL et afficher une fenêtre. Si vous avez un souci, vérifiez que toutes les ressources supplémentaires sont bien présentes et posez votre question dans le fil de discussion.

4-6. Suppléments

  • GLFW: Window Guide : guide officiel GLFW pour afficher une fenêtre avec GLFW.
  • Compiler vos applications C : informations sur les processus de compilation et éditions de liens avec une liste des erreurs possibles.
  • GLFW with Code::Blocks : utiliser GLFW avec l’IDE Code::Blocks.
  • Running CMake : aperçu de CMake sur Windows et Linux.
  • Writing a build system under Linux : un tutoriel de Wouter Verholst pour écrire un système de construction de solution sur Linux, conçu pour ces tutoriels.
  • Polytonic/Glitter : un projet simple préconfiguré avec toutes les bibliothèques.


Apprendre OpenGL

Table des matières

  • II. Affichage de texte
    • II-A. Affichage classique de texte : images de fontes
    • II-B. Affichage moderne de texte : FreeType
      • II-B-1. Les shaders
      • II-B-2. Afficher une ligne de texte
    • II-C. Pour aller plus loin
    • II-D. Remerciements

    • II. Affichage de texte

    Lors de certaines étapes de votre aventure graphique, vous souhaiterez afficher du texte avec OpenGL. Contrairement à ce que vous pourriez penser, afficher une simple chaîne de caractères à l’écran est assez difficile avec une bibliothèque de bas niveau comme OpenGL. S’il vous suffit d’afficher les 128 premiers caractères, ce n’est pas trop compliqué. Les choses se corsent dès que chaque caractère peut avoir ses propres largeur, hauteur et marge. Selon votre langue, vous pourrez avoir besoin de plus de 128 caractères, ou encore vouloir afficher des expressions mathématiques ou des partitions de musique, voire écrire de haut en bas. Si vous prenez tout cela en compte, cela ne vous surprendra pas qu’une API de bas niveau comme OpenGL ne propose pas ces fonctionnalités.

    Puisqu’il n’existe pas dans OpenGL d’outils pour le texte, il nous appartient de définir un système pour afficher du texte à l’écran. Nous n’avons pas de primitive graphique pour les caractères, il va falloir être créatif. Voici quelques idées : tracer des lettres avec GL_LINES, créer des mailles 3D pour les lettres ou afficher des textures de caractères dans un environnement 3D.

    Le plus souvent, les développeurs choisissent d’utiliser des textures pour afficher les caractères dans des rectangles 2D. Cela n’est pas trop difficile, mais obtenir les bons caractères sur une texture peut s’avérer délicat. Dans ce chapitre, nous verrons plusieurs méthodes et nous implémenterons une technique plus évoluée, mais aussi plus souple pour afficher du texte, avec la bibliothèque FreeType.

    II-A. Affichage classique de texte : images de fontes

    Auparavant, afficher du texte consistait à choisir une fonte (ou en créer une) pour votre application puis en extraire les caractères et les coller dans une seule grande texture. Une telle texture, que l’on peut appeler une image de fonte (bitmap font) contient tous les symboles de caractères dans des zones prédéfinies de la texture. Ces symboles sont appelés des glyphes. Chaque glyphe est positionné à un endroit précis de la texture. Quand vous souhaitez afficher un caractère, vous le glyphe correspondant en affichant cette partie de l’image de fonte dans un rectangle 2D.

    Vous voyez ici comment afficher le texte « OpenGL » avec une telle fonte, en sélectionnant chacun des glyphes de la texture (au moyen de leurs coordonnées relatives à la texture), pour être affichés dans des petits rectangles. En activant le blending et avec un fond transparent, on obtient juste une chaîne affichée à l’écran. Cette fonte graphique a été générée avec le générateur de fonte de Codehead.

    Cette approche a ses avantages et inconvénients. En premier lieu, c’est assez facile à mettre en œuvre et comme les images sont déjà prérendues, c’est assez performant. Cependant, ce n’est pas très souple. Si vous voulez changer de fonte, vous devez recompiler une fonte complète et de plus, ce système est limité à une seule résolution ; zoomer donnera très vite une image pixelisée. Enfin, on est limité à un petit ensemble de caractères et donc les caractères étendus ou Unicode ne sont pas envisageables.

    Cette approche était (et reste) très courante, car elle est rapide et fonctionne sur toutes les machines, mais aujourd’hui des techniques plus souples existent. L’une d’entre elles consiste à charger des fontes TrueType grâce à la bibliothèque FreeType.

    II-B. Affichage moderne de texte : FreeType

    FreeType est une bibliothèque de développement logiciel capable de charger des fontes, de les afficher dans une image bitmap et d’offrir un support pour les opérations relatives à ces fontes. C’est une bibliothèque largement diffusée, utilisée dans Mac OS X, Java, les consoles PlayStation, Linux et Android pour ne citer que ceux-là. Ce qui rend FreeType très intéressante est sa capacité à charger les fontes TrueType.

    Une fonte TrueType est un ensemble de glyphes de caractères, non pas définis par des pixels ni par toute sorte de solutions statiques, mais par des équations mathématiques (des combinaisons de splines). Comme des images vectorielles, les images d’une fonte peuvent être générées de la taille que l’on veut, permettant d’obtenir des glyphes de taille quelconque sans perte de qualité.

    FreeType peut être téléchargée à partir de ce site web. Vous pouvez compiler la bibliothèque vous-même à partir de leur code source ou utiliser leurs bibliothèques précompilées si votre système cible est dans la liste. Assurez-vous de lier freetype.lib et que votre compilateur pourra trouver les fichiers d’en-tête.

    Incluez les en-têtes prévus :

    #include <ft2build.h>
#include FT_FREETYPE_H

    De la façon dont FreeType a été construite (du moins à l’heure où j’écris), vous ne pouvez pas placer les fichiers en-tête dans un nouveau répertoire, il faut les placer dans le répertoire racine de vos fichiers d’en-tête.

    #include <FreeType/ft2build.h> causera sans doute de sérieux conflits.

    FreeType charge ces fontes TrueType et pour chaque glyphe génère une image bitmap et calcule plusieurs tailles. On peut extraire ces images pour générer des textures et positionner chaque glyphe de caractère en bonne place selon les tailles choisies.

    Pour charger une fonte, il suffit d’initialiser la bibliothèque FreeType et charger la fonte en tant que face, comme FreeType les nomme. Ici nous chargeons la fonte TrueType arial.ttf qui a été copiée depuis le répertoire Windows/Fonts.

    FT_Library ft;
if (FT_Init_FreeType(&ft))
    std::cout << "ERROR::FREETYPE: Could not init FreeType Library" << std::endl;
FT_Face face;
if (FT_New_Face(ft, "fonts/arial.ttf", 0, &face))
    std::cout << "ERROR::FREETYPE: Failed to load font" << std::endl;

    Chacune de ces fonctions FreeType retourne un entier non nul si une erreur survient.

    Après avoir chargé la face, on doit définir la taille que nous voulons extraire de cette face :

    FT_Set_Pixel_Sizes(face, 0, 48);

    La fonction détermine les paramètres de hauteur et largeur de la fonte. Si width est laissé à 0, la face calcule dynamiquement la largeur en fonction de la hauteur.

    Une face FreeType contient un ensemble de glyphes. On peut choisir un de ces glyphes comme étant le glyphe courant en appelant FT_Load_Char(). Ici, on choisit le glyphe du caractère ‘X’ :

    if (FT_Load_Char(face, 'X', FT_LOAD_RENDER))
    std::cout << "ERROR::FREETYTPE: Failed to load Glyph" << std::endl;

    En utilisant FT_LOAD_RENDER comme un des paramètres de chargement, nous demandons à FreeType de créer une image bitmap 8 bits en niveaux de gris, que nous pourrons accéder par face->glyph->bitmap.

    Les glyphes que nous chargeons avec FreeType n’ont cependant pas la même taille (ce qui était le cas avec les images de fontes). L’image générée par FreeType est juste assez large pour contenir la partie visible du caractère. Par exemple, l’image du caractère ‘.’ est bien plus petite que celle du caractère ‘X’. Pour cette raison, FreeType charge aussi plusieurs tailles en spécifiant la largeur de chaque caractère et comment les positionner correctement. Ci-dessous une image FreeType qui montre toutes les dimensions calculées pour chaque glyphe.

    Chaque glyphe est défini par rapport à une ligne horizontale (figurée par la flèche horizontale), certains sont placés exactement sur cette ligne de base (comme ‘X’), et d’autres sont légèrement en dessous (comme ‘g’ ou ‘p’). Ces dimensions définissent exactement les décalages pour placer chaque glyphe sur la ligne de base, la largeur de chaque glyphe, et combien de pixels sont nécessaires avant le glyphe suivant. Ci-dessous une courte liste des propriétés dont on aura besoin :

    • width : la largeur (en pixels) de l’image, accessible par face->glyph->bitmap.width.
    • height : la hauteur (en pixels) de l’image, accessible par face->glyph->bitmap.rows.
    • bearingX : le repère gauche, c’est-à-dire la position horizontale (en pixels) de l’image relative à l’origine, accessible par face->glyph->bitmap_left.
    • bearingY : l’œil, c’est-à-dire la position verticale (en pixels) de l’image relative à l’origine, accessible par face->glyph->bitmap_top.
    • Advance : l’empattement, c’est-à-dire la distance horizontale (en 1/64e de pixels) de l’origine du caractère au caractère suivant, accessible par face->glyph->advance.x.

    On pourrait charger un glyphe, retrouver ses dimensions et générer une texture à chaque fois que l’on voudrait afficher un caractère à l’écran, mais ce serait peu efficace de faire cela à chaque image. Il vaut mieux mémoriser les données quelque part et les reprendre à chaque affichage du caractère. Nous définissons une structure que nous mémorisons dans une map.

    struct Character {
 GLuint     TextureID;    // Identificateur de la texture du glyphe
 glm::ivec2 Size;          // Taille du glyphe
 glm::ivec2 Bearing;    // Approche gauche et œil du glyphe
 GLuint     Advance;     // Empattement du glyphe
};
std::map<GLchar, Character> Characters;

    Dans ce tutoriel, nous simplifions en nous limitant aux 128 premiers caractères de la table ASCII. Pour chaque caractère, nous générons une texture et mémorisons les données dans une structure que nous ajoutons à la table de hachage Characters. De cette façon, toutes les données nécessaires pour afficher chaque caractère seront disponibles pour un usage ultérieur.

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // Désactiver la restriction d’alignement d’octets
for (GLubyte c = 0; c < 128; c++)
{
    // Chargement du glyphe du caractère
    if (FT_Load_Char(face, c, FT_LOAD_RENDER))
    {
        std::cout << "ERROR::FREETYTPE: Failed to load Glyph" << std::endl;
        continue;
    }
    // Générer la texture
   GLuint texture;
   glGenTextures(1, &texture);
   glBindTexture(GL_TEXTURE_2D, texture);
   glTexImage2D( GL_TEXTURE_2D,
                            0,
                            GL_RED,
                            face→glyph→bitmap.width,
                            face→glyph→bitmap.rows,
                            0,
                            GL_RED,
                            GL_UNSIGNED_BYTE,
                            face→glyph→bitmap.buffer);
    // Fixer les options de la texture
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    // Mémoriser le caractère pour un usage ultérieur
    Character character = {
        texture,
        glm::ivec2(face->glyph->bitmap.width, face→glyph→bitmap.rows),
        glm::ivec2(face->glyph->bitmap_left, face→glyph→bitmap_top),
        face→glyph→advance.x
        };
    Characters.insert(std::pair<GLchar, Character>(c, character));
}

    On boucle sur les 128 caractères de la table ASCII et on retrouve leur glyphe. Pour chaque caractère, on génère une texture, on positionne les options et les dimensions. À noter que l’on utilise GL_RED pour l’argument internalFormat de la texture, ainsi que pour l’argument format. L‘image générée à partir du glyphe est une image 8 bits en niveaux de gris où chaque couleur est représentée sur un octet. Pour cette raison, nous mémoriserons chaque octet de l’image comme la valeur d’une couleur de texture. Nous réalisons cela en créant une texture dans laquelle chaque octet correspond à la composante rouge (premier octet du vecteur couleur). Puisque nous n’utilisons qu’un seul octet pour représenter les couleurs de la texture, il faut prendre garde à une restriction d’OpenGL :

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

    OpenGL requiert que les textures aient une taille multiple de 4 octets (pour des raisons d’alignement). En principe ce n’est pas un problème puisque la plupart des textures ont une taille multiple de 4 et/ou utilisent 4 octets par pixel, mais puisque nous n’utilisons qu’un octet par pixel, elles peuvent avoir n’importe quelle taille. En paramétrant l’absence d’alignement à 1, nous assurons qu’il n’y aura pas de problème d’alignement (ce qui provoquerait des erreurs en mémoire).

    Assurez-vous de supprimer les ressources FreeType quand vous avez fini de traiter les glyphes :

    FT_Done_Face(face);
FT_Done_FreeType(ft);

    II-B-1. Les shaders

    Pour effectivement afficher les glyphes, nous utiliserons le vertex shader suivant :

    #version 330 core
layout (location = 0) in vec4 vertex; // <vec2 pos, vec2 tex>
out vec2 TexCoords;
uniform mat4 projection;
void main()
{
    gl_Position = projection * vec4(vertex.xy, 0.0, 1.0);
    TexCoords = vertex.zw;
}

    Nous combinons la position et les cordonnées de texture dans un vec4. Le vertex shader multiplie les coordonnées par une matrice de projection et transmet les coordonnées de texture au fragment shader :

    #version 330 core
in vec2 TexCoords;
out vec4 color;
uniform sampler2D text;
uniform vec3 textColor;
void main()
{
    vec4 sampled = vec4(1.0, 1.0, 1.0, texture(text, TexCoords).r);
    color = vec4(textColor, 1.0) * sampled;
}

    Le fragment shader utilise deux variables uniformes : l’une pour l’image du glyphe en niveaux de gris, l’autre est une couleur pour ajuster la couleur finale du texte. Nous échantillonnons d’abord la couleur de la texture. Cette couleur est codée sur la seule composante rouge, nous utilisons la composante r de la texture comme valeur de transparence. En jouant sur la valeur alpha de la couleur, la couleur résultante sera transparente pour la couleur du fond des glyphes et opaque pour la couleur des pixels du caractère. Nous multiplions aussi les couleurs RGB par la variable uniforme textColor pour la couleur du texte.

    Il faut activer le blending pour que cela fonctionne :

    glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    Pour la matrice de projection, nous choisissons une projection orthogonale. Pour afficher du texte, nul besoin de perspective (en général) et l’utilisation d’une projection orthogonale nous permet de spécifier les coordonnées des sommets directement en coordonnées d’affichage si nous la définissons ainsi :

    glm::mat4 projection = glm::ortho(0.0f, 800.0f, 0.0f, 600.0f)

    Nous fixons le bas de la projection à 0.0f et le haut égal à la hauteur de la fenêtre d’affichage. Par conséquent, nous spécifions les coordonnées avec des valeurs de y s’étendant du bas de la fenêtre (0.0f) jusqu’en haut (600.0f). Et donc le point (0.0, 0.0) correspond au coin en bas à gauche.

    Il reste à créer un VBO et un VAO pour le rendu des rectangles. Pour l’instant, nous réservons assez de mémoire en initialisant le VBO de façon à pouvoir mettre à jour la mémoire du VBO lors du rendu des caractères.

    GLuint VAO, VBO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 6 * 4, NULL, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);

    Les rectangles 2D nécessitent 6 sommets de 4 réels chacun, on réserve ainsi 6*4 réels en mémoire. Étant donné que nous mettrons à jour très souvent le contenu du VBO, on alloue la mémoire avec GL_DYNAMIC_DRAW.

    II-B-2. Afficher une ligne de texte

    Pour afficher un caractère, on extrait la structure correspondante de la table de hachage Characters et on calcule les dimensions du rectangle en utilisant les dimensions du caractère. On peut ensuite générer dynamiquement un ensemble de six sommets que nous utiliserons pour rafraîchir le contenu de la mémoire gérée par le VBO avec glBufferSubData().

    On crée une fonction RenderText() pour afficher une chaîne de caractères :

    void RenderText(Shader &s, std::string text, GLfloat x, GLfloat y, GLfloat scale, glm::vec3 color)
{
    // Activation du rendu
    s.Use();
    glUniform3f(glGetUniformLocation(s.Program, "textColor"), color.x, color.y, color.z);
    glActiveTexture(GL_TEXTURE0);
    glBindVertexArray(VAO);
    // Boucle sur tous les caractères
 std::string::const_iterator c;
 for (c = text.begin(); c != text.end(); c++)

--------------------------------------------------------------------------------- Modif

  {
        Character ch = Characters[*c];
        GLfloat xpos = x + ch.Bearing.x * scale;
        GLfloat ypos = y - (ch.Size.y - ch.Bearing.y) * scale;
        GLfloat w = ch.Size.x * scale;
        GLfloat h = ch.Size.y * scale;
        // Mise à jour du VBO
        GLfloat vertices[6][4] = {
            { xpos,     ypos + h,   0.0, 0.0 },
            { xpos,     ypos,       0.0, 1.0 },
            { xpos + w, ypos,       1.0, 1.0 },
            { xpos,     ypos + h,   0.0, 0.0 },
            { xpos + w, ypos,       1.0, 1.0 },
            { xpos + w, ypos + h,   1.0, 0.0 }
       };
        // Rendu du glyphe sur le rectangle
        glBindTexture(GL_TEXTURE_2D, ch.textureID);
        // Mise à jour de la mémoire du VBO
        glBindBuffer(GL_ARRAY_BUFFER, VBO);
        glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices) ;
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        // Rendu du rectangle
        glDrawArrays(GL_TRIANGLES, 0, 6);
        // Avancer le curseur au glyphe suivant (noter que l’avance est calculée en 1/64e pixels)
        x += (ch.Advance >> 6* scale; >// Décalage à droite de 6 pour obtenir la valeur en pixels
    }

--------------------------------------------------------------------------------- Modif

    glBindVertexArray(0);
    glBindTexture(GL_TEXTURE_2D, 0);
}

    Le contenu de la fonction devrait parler de lui-même : nous calculons la position de l’origine du rectangle (xposyposwh et ) ainsi que sa taille ( et ) puis on génère un ensemble de six sommets pour former le rectangle ; noter que nous effectuons une mise à l’échelle avec scale. On met à jour le contenu du VBO et on affiche le rectangle.

    Le code suivant mérite cependant un peu d’attention :

    GLfloat ypos = y - (ch.Size.y - ch.Bearing.y);

    Certains caractères (comme ‘p’ ou ‘g’) sont affichés légèrement en dessous de la ligne de base, et le rectangle doit aussi être positionné légèrement décalé en y. La valeur de ce décalage de ypos peut être déterminée avec les dimensions du glyphe :

    Cette distance, c’est-à-dire le décalage que l’on doit appliquer vers le bas, est figurée par la flèche rouge. Comme on le voit, cette distance se calcule comme la différence entre la hauteur du glyphe et la dimension bearingY. Cette valeur est nulle pour les caractères qui sont sur la ligne de base alors qu’elle est positive pour les caractères comme ‘g’ ou ‘j’.

    Si vous avez tout exécuté correctement, vous devriez pouvoir afficher des chaînes de caractères avec les lignes suivantes :

    RenderText(shader, "This is sample text", 25.0f, 25.0f, 1.0f, glm::vec3(0.5, 0.8f, 0.2f));
RenderText(shader, "(C) LearnOpenGL.com", 540.0f, 570.0f, 0.5f, glm::vec3(0.3, 0.7f, 0.9f));

    Vous devriez obtenir l’image suivante :

    Image d'un rendu de texte avec OpenGL et FreeType.

    Vous trouverez le code de cet exemple ici.

    Pour vous donner une idée du calcul des sommets des rectangles, on peut désactiver le blending pour mieux voir leurs dimensions et leur position :

    Image du rendu des rectangles sans transparence pour le rendu du texte en OpenGL.

    Ici, on voit bien que la plupart des rectangles reposent sur la ligne de base tandis que certains sont décalés vers le bas.

    II-C. Pour aller plus loin

    Ce chapitre montre une technique d’affichage de texte avec les fontes TrueType en utilisant la bibliothèque FreeType. Cette approche est souple, permet la mise à l’échelle, et fonctionne avec beaucoup de codes de caractères. Cependant, elle peut s’avérer excessive pour votre application, car elle requiert de générer et rendre une texture pour chaque glyphe.

    Les images de fontes sont plus simples et rapides, car elles ne requièrent qu’une seule texture pour tous les caractères. La meilleure approche est de combiner les deux façons en générant dynamiquement une texture d’image de fonte représentant tous les glyphes de caractères chargés avec FreeType. Cela décharge le moteur de rendu de tous ces changements de texture et selon comment les glyphes sont rangés peut améliorer nettement les performances.

    Une autre question avec les fontes FreeType vient de ce que les textures sont mémorisées avec une fonte de taille fixe et une mise à l’échelle importante conduit à des bords crénelés. De plus, les rotations appliquées aux glyphes les rendront flous. Cela peut être atténué : au lieu de mémoriser la couleur réelle des pixels, on mémorise la distance au contour le plus proche du glyphe pour chaque pixel. Cette technique est appelée « signed distance fields », et Valve a publié un article voilà quelques années sur sa mise en œuvre qui fonctionne très bien pour des applications de rendus 3D.


Vu dans ... https://pub.phyks.me/sdz/sdz/creez-des-programmes-en-3d-avec-opengl.html

Tutoriel : Créez des programmes en 3D avec OpenGL

Table des matières

Créez des programmes en 3D avec OpenGL
Introduction à OpenGL
Qu'est-ce qu'OpenGL ?
Que dois-je installer ?
Première application OpenGL avec SDL
Notions de base
Couleur et interpolation
Vertices et polygones
Fonctions et types
Les transformations
Un brin de folie mathématique
Les matrices OpenGL
Les transformations
Exercice : une grue
Enfin de la 3D (Partie 1/2)
Du réel à l'écran
La perspective
Placer la caméra
Enfin de la 3D (Partie 2/2)
Un cube
Le Z-Buffer
Animation
Les textures
Charger une texture
Plaquage de texture
Texture répétitive
Les couleurs
Les quadriques
Principe d'utilisation
Les quadriques
Exercice : une roquette
Contrôle avancé de la caméra (Partie 1/2)
Principe d'une caméra TrackBall
Quelques bases de C++
Implémentation de la caméra
Scène de test
Contrôle avancé de la caméra (Partie 2/2)
Principe d'une caméra FreeFly
Gestion fluide du mouvement
Implémentation de la caméra
Scène de test
La trigonométrie
Trigo dans un triangle rectangle
Le cercle trigonométrique
Systèmes de coordonnées
Les matrices
L'outil matrice
Transformations
Combinaison de transformations
Créer une vidéo de votre programme
Enregistrer la vidéo
Encoder la vidéo

Créez des programmes en 3D avec OpenGL

L'envie vous prend soudainement d'ajouter une nouvelle dimension à votre vie mais vous avez peur de vous lancer car vous pensez que faire des programmes en 3D est réservé à l'élite ?
Rassurez-vous l'apprentissage de la programmation 3D via OpenGL est relativement simple et par le biais de ce tutoriel vous connaîtrez les bases d'OpenGL ainsi que de multiples techniques utilisées dans les jeux vidéo.

Bonne lecture et surtout bonne création ! :soleil:

Ce cours nécessite la lecture préalable du cours de C / C++ rédigé par M@teo21. Il faut avoir lu au moins les trois premières parties (partie sur la SDL incluse).

Introduction à OpenGL

Qu'est-ce qu'OpenGL ?

Dans ce chapitre nous découvrirons brièvement les possiblités d'OpenGL et réaliserons notre première application OpenGL grâce à SDL.

Qu'est-ce qu'OpenGL ?

Introduction à OpenGL Que dois-je installer ?

Qu'est-ce qu'OpenGL ?

OpenGL (Open Graphics Library) est une blibliothèque graphique très complète qui permet aux programmeurs de développer des applications 2D, 3D assez facilement.
Vous avez déjà dû l'utiliser ou en entendre parler, car de nombreux jeux, comme Quake III, proposent l'OpenGL comme mode d'affichage.

Quake III Arena

Bon d'accord, je dois vous l'avouer après avoir lu ce tuto vous ne saurez pas encore faire des jeux comme ça (notamment à cause du moteur physique pour gérer les collisions, déplacements et projectiles) mais il est beau de rêver. :p

OpenGL s'utilise principalement en C++, c'est pourquoi il est conseillé de connaître ce langage. Mais rassurez-vous ! Même sans base vous arriverez parfaitement à comprendre tous les exemples du cours, cependant les applications que vous serez amenés à développer par vous-mêmes seront peut-être moins poussées. La lecture du tuto C/C++ est fortement recommandée.

Il est possible d'utiliser OpenGL en Python, Delphi, Java, et les commandes y sont similaires. Ici nous utiliserons le C/C++ pour profiter du cours déjà présent sur le site.

Une multitude de fonctionnalités

OpenGL dispose de nombreuses fonctions que vous pourrez « facilement » utiliser, notamment la gestion de :

  • la caméra ;

  • la rotation 3D des objets ;

  • le remplissage des faces ;

  • les textures ;

  • la lumière ;

  • et bien plus encore...

Dans ce tuto je compte vous montrer un maximum de techniques telles que la génération d'un terrain 3D, un moteur de particules, le bump-mapping, le cell-shading, etc. Comment alors ne pas saliver devant toutes ces possibilités qui s'ouvrent à vous ? D'autant que la plupart sont très accessibles d'un point de vue difficulté de programmation.

D'un point de vue pédagogique, nous ne verrons la 3D qu'à partir du 4e chapitre, car des connaissances de base sont nécessaires afin de se lancer correctement.

Fenêtrage et événements

OpenGL ne fournit que des fonctions 3D qui doivent être exécutées dans un contexte graphique déjà créé. Il nous faut donc choisir un système pour créer les fenêtres et gérer les événements pour donner une interactivité aux applications. OpenGL étant implémenté sur de nombreuses plates-formes, j'ai choisi de vous faire utiliser la SDL (d'autant que son installation/utilisation vous est enseignée dans le cours de M@teo).

La SDL nous permettra ainsi :

  • de créer une fenêtre ;

  • de charger très facilement des textures grâce à SDL_image ;

  • d'utiliser le clavier et la souris ;

  • d'animer nos scènes.

Ainsi nous combinerons la facilité d'utilisation de la SDL avec la puissance d'OpenGL tout en gardant le coté multi plate-formes.

Et les moteurs 3D ?

Je reviendrai en détail plus tard dans ce tutoriel sur ce qui diffère OpenGL des moteurs 3D et sur comment créer son moteur 3D basé sur OpenGL. Quoi qu'il en soit ce que vous apprendrez ici est souvent applicable ailleurs et vous fournit des explications générales sur la programmation 3D. Se plonger dans un moteur tel Irrlicht ou Ogre sera d'autant plus facile que vous saurez ce qu'ils utilisent derrière.

Introduction à OpenGL Que dois-je installer ?

Que dois-je installer ?

Qu'est-ce qu'OpenGL ? Première application OpenGL avec SDL

Que dois-je installer ?

Vous êtes enfin décidés à vous lancer ? ^^ Parfait !

Les fichiers nécessaires pour développer

Si vous avez téléchargé Code::Block ou DevC++pour suivre le tuto de C/C++ ne changez rien, vous avez déjà les headers et les .a nécessaires. Vous devez donc avoir :

OS | include | lib
Windows | GL/gl.h GL/glu.h | libopengl32.a* libglu32.a
Unix** | libopengl.a libglu.a

* Si vous utilisez Visual Studio, vous devez déjà avoir l'équivalent en .lib.
** Nous verrons lors de la création de notre premier programme quoi rajouter exactement sous Linux.

Les fichiers nécessaires pour exécuter

  • Sous Windows sauf cas rare très exceptionnel vous avez déjà les .dll nécessaires soit : opengl32.dll et glu32.dll. Vous n'aurez pas à les fournir avec votre exécutable, car ils sont présents par défaut sous Windows.

    Vous devrez continuer à fournir les .dll de la SDL.

  • Sous Linux il vous faut les .so associés au .a.

Vérifier que l'accélération 3D est activée

Avoir les bons fichiers ne signifie pas forcément que les applications 3D tourneront parfaitement. Si l'accélération matérielle n'est pas activée, les performances en seront très fortement diminuées.
Vérifiez donc que vous avez bien installé les derniers drivers de votre carte graphique. Sous Windows si vous avez l'habitude de jouer à des jeux vidéos sans problèmes c'est que c'est bon ! :D
Sous Linux utilisez le fameux programme glxgears pour tester. Les instructions étant trop dépendantes de votre distribution et de votre carte, reportez-vous aux nombreuses ressources disponibles sur Internet.

Qu'est-ce qu'OpenGL ? Première application OpenGL avec SDL

Première application OpenGL avec SDL

Que dois-je installer ? Notions de base

Première application OpenGL avec SDL

Je vous l'ai dit plus haut nous allons utiliser OpenGL dans un contexte SDL. La première chose à faire est donc de créer un projet SDL de base (comme expliqué dans le tuto de M@teo). Nous viendrons y remplacer tout le code et compléter les options de compilation pour rajouter OpenGL lors de la phase d'édition des liens.

Si vous utilisez un IDE qui fournit des templates de projet (comme Code::Blocks), ne choisissez pas un projet OpenGL mais bien SDL. Nous le compléterons pour avoir un projet SDL/OpenGL.

Je fournirai d'ailleurs en fin de chapitre le projet final utilisé et vous pourrez vous en servir comme point de départ pour vos applications OpenGL créées en suivant ce tutoriel.

Code de départ

Souvenez-vous du code minimal pour ouvrir une fenêtre SDL :

#include <SDL/SDL.h>
int main(int argc, char *argv[])
{
    SDL_Init(SDL_INIT_VIDEO);
    SDL_Surface* ecran = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE);
    SDL_Flip(ecran);
    bool continuer = true;
    SDL_Event event;
    while (continuer)
    {
        SDL_WaitEvent(&event);
        switch(event.type)
        {
            case SDL_QUIT:
                continuer = false;
        }
    }
    SDL_Quit();
    return 0;
}

Je ne respecte volontairement pas les règles du C car je compile en C++ et vous inviterai à en faire autant dans le futur pour suivre ce tutoriel. En effet les concepts que nous verrons plus tard seront beaucoup plus facilement implémentables en C++. Si vous ne connaissez que le C pour l'instant, rassurez-vous, nous introduirons le concept « objet » de manière simple et intuitive.

Initialiser SDL en mode OpenGL

La première chose à changer est au niveau de l'initialisation de mode vidéo. Nous allons utiliser SDL_OPENGL au lieu de SDL_HWSURFACE :

SDL_SetVideoMode(640, 480, 32, SDL_OPENGL);

Que devient le SDL_DOUBLEBUF utilisé avec la SDL normalement pour activer le double-buffering (cf. Travailler avec le double buffer) ?

L'équivalent avec OpenGL reviendrait à appeler SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 ); mais il s'avère qu'il est déjà activé par défaut pour OpenGL. Rien de spécial à faire donc.

Le premier dessin

Ensuite nous allons faire nos premiers appels OpenGL pour dessiner un simple triangle.
Nous rajoutons donc les headers nécessaires :

#include <GL/gl.h>
#include <GL/glu.h>

Avec Visual Studio (uniquement), il faut rajouter #include <windows.h> avant d'inclure les headers OpenGL.

Voici le code du triangle qui vous sera expliqué dans le prochaine chapitre :

glClear(GL_COLOR_BUFFER_BIT);
    glBegin(GL_TRIANGLES);
        glColor3ub(255,0,0);    glVertex2d(-0.75,-0.75);
        glColor3ub(0,255,0);    glVertex2d(0,0.75);
        glColor3ub(0,0,255);    glVertex2d(0.75,-0.75);
    glEnd();

SDL_Flip(ecran); disparaît au profit de l'appel suivant :

glFlush();
    SDL_GL_SwapBuffers();

La première commande, glFlush, vient s'assurer que toutes les commandes OpenGL ont été exécutées, et SDL_GL_SwapBuffers est l'équivalent de l'ancien SDL_Flip.

Le code complet
#include <SDL/SDL.h>
#include <GL/gl.h>
#include <GL/glu.h>
int main(int argc, char *argv[])
{
    SDL_Init(SDL_INIT_VIDEO);
    SDL_WM_SetCaption("Mon premier programme OpenGL !",NULL);
    SDL_SetVideoMode(640, 480, 32, SDL_OPENGL);
    bool continuer = true;
    SDL_Event event;
    while (continuer)
    {
        SDL_WaitEvent(&event);
        switch(event.type)
        {
            case SDL_QUIT:
                continuer = false;
        }
         glClear(GL_COLOR_BUFFER_BIT);
        glBegin(GL_TRIANGLES);
            glColor3ub(255,0,0);    glVertex2d(-0.75,-0.75);
            glColor3ub(0,255,0);    glVertex2d(0,0.75);
            glColor3ub(0,0,255);    glVertex2d(0.75,-0.75);
        glEnd();
        glFlush();
        SDL_GL_SwapBuffers();
    }
    SDL_Quit();
    return 0;
}
Compilation

Pour compiler il nous faut rajouter les fichiers cités plus haut. Dans nos options de projet nous rajoutons donc :

IDE / Compilateur | lib
Code::Blocks Windows | opengl32 glu32
DevC++ Windows | -lopengl32 -lglu32
Visual Studio Windows | opengl32.lib glu32.lib
Code::Blocks Linux | GL GLU
gcc / g++ Linux | -lGL -lGLU
Résultat

Téléchargez le projet Code::Blocks final, l'exécutable Windows et le Makefile Unix (116 Ko)

Pour les utilisateurs de Code::Blocks, vous pouvez ouvrir le projet fourni et faire Project > Save project as user-template pour créer un projet de départ pour toutes vos applications SDL/OpenGL. Ainsi plus tard vous n'aurez qu'à faire File > New Project... > (Onglet) User templates > Votre template pour créer un projet opérationnel immédiatement.

Et voilà ce n'était pas sorcier ! :p Direction le prochain chapitre pour comprendre le code OpenGL que nous avons utilisé et pour dessiner plein de jolies choses.

Notions de base

Première application OpenGL avec SDL Couleur et interpolation

Nous allons voir dans ce chapitre quelques concepts de base en OpenGL.
Bien que les exemples donnés soient en 2D vous verrez qu'ils seront toujours applicables quand nous passerons à la vraie 3D.

Couleur et interpolation

Notions de base Vertices et polygones

Couleur et interpolation

Dans le screenshot final du chapitre précédent (le triangle coloré) vous avez pu admirer la pureté et la beauté des couleurs en OpenGL.

Pour définir la couleur d'un vertex on utilise :

glColor3ub(rouge, vert, bleu);

Les valeurs de rouge, vert et bleu sont des entiers entre 0 et 255.
Nous aurions pu aussi bien utiliser glColor3f (avec donc des composantes de couleurs entre 0 et 1) mais la plage de valeur [0..255] est plus facile à comprendre car fortement utilisée dans les logiciels de dessins.

À partir d'une couleur précise, pour connaître ses composantes RVB (ou RGB en anglais) il suffit d'utiliser la palette de couleurs de Windows (par exemple en utilisant Paint).

Un appel à glColor affecte la couleur courante, celle qui sera utilisée pour les vertices qui seront définis après. On peut ainsi changer de couleur à chaque vertex ou juste de temps en temps.

Et pour les points qui ne sont pas des sommets, comment la couleur est-elle définie ?

Les couleurs des points qui constituent une face sont calculées par interpolation des couleurs des sommets.

Citation : Dictionnaire

Interpolation : évaluation de la valeur d'une fonction entre deux points de valeurs connues.

Cela donne donc lieu à de jolis dégradés entre les sommets.

Exercice

Modifier le code du chapitre précédent pour dessiner un rectangle avec un dégradé bleu/rouge.

Corrigé
#include <SDL/SDL.h>
#include <GL/gl.h>
#include <GL/glu.h>

int main(int argc, char *argv[])
{
    SDL_Init(SDL_INIT_VIDEO);
    SDL_WM_SetCaption("Un joli \"carr\u00E9\"",NULL);
    SDL_SetVideoMode(640, 480, 32, SDL_OPENGL);

    bool continuer = true;
    SDL_Event event;

    while (continuer)
    {
        SDL_WaitEvent(&event);
        switch(event.type)
        {
            case SDL_QUIT:
                continuer = false;
        }

         glClear(GL_COLOR_BUFFER_BIT);

        glBegin(GL_QUADS);
            glColor3ub(0,0,255);
            glVertex2d(-0.75,-0.75);
            glVertex2d(-0.75,0.75);
            glColor3ub(255,0,0);
            glVertex2d(0.75,0.75);
            glVertex2d(0.75,-0.75);
        glEnd();

        glFlush();
        SDL_GL_SwapBuffers();
    }

    SDL_Quit();

    return 0;
}

Un dégradé très facilement avec OpenGL

Téléchargez le projet Code::Blocks, l'exécutable Windows et le Makefile Unix (116 Ko)

Euh oui mais ton carré n'est pas vraiment carré là !?

En effet par défaut les coordonnées vont de -1 à 1 à la fois sur X et sur Y quel que soit le ratio (rapport largeur/hauteur) de la fenêtre. Nous verrons plus tard comment tenir compte du ratio de la fenêtre et changer les coordonnées min/max.

Vous pouvez dès à présent vous amuser à dessiner des formes en 2D dans cet intervalle de coordonnées. Jouez avec les couleurs, les formes, les dégradés.

Notions de base Vertices et polygones

Vertices et polygones

Couleur et interpolation Fonctions et types

Vertices et polygones

En OpenGL tout est à base de polygones, le polygone de base le plus utilisé étant le triangle.
Même un model 3D se représente avec des polygones qui en composent les faces.

Terrain en mode filaire*

* Nous verrons dans la partie II de ce tuto comment générer un terrain en OpenGL.

Pour dessiner un polygone il faut :

  • définir tous les sommets qui le composent ;

  • indiquer à OpenGL comment il doit utiliser ses sommets.

Sommet se dit vertex en anglais, vertices au pluriel. Pour rester proches du langage OpenGl et du monde de la 3D, nous garderons l'appellation anglaise. Ça fait plus pro, isn't it ? :soleil:

Regardons la 1re ligne du code barbare du chapitre précedent :

glBegin(GL_TRIANGLES);

Avec GL_TRIANGLES on indique à OpenGL qu'il doit faire des triangles avec les vertices qui seront déclarés.

Voici la liste des modes que nous pouvons utiliser :

mode | détail
GL_POINTS | chaque vertex sera représenté comme un point
GL_LINES | les vertices seront reliés 2 à 2 pour faire des lignes. Si on définit 4 vertices, le 1er sera relié avec le 2nd, le 3e avec le 4e, mais en aucun cas le 2e ne sera relié au 3e
GL_LINE_STRIP | les vertices sont connectés par des lignes du 1er au dernier. Si on définit 3 vertices, le 1er sera relié avec le 2nd et le 2nd avec le 3e
GL_LINE_LOOP | identique à GL_LINE_STRIP, mais le dernier vertex est aussi relié au 1er (on réalise une boucle : loop)
GL_TRIANGLES | les triplets de vertices sont utilisés pour former des triangles pleins. Ce mode sera le plus utilisé lorsque nous représenterons des objets complexes à base de faces triangulaires
GL_TRIANGLE_STRIP | Les triangles se touchent, c'est-à-dire qu'il faut 3 vertices pour faire un premier triangle, et un 4e vertex suffit pour en définir un autre car les 2 derniers vertices seront aussi utilisés
GL_TRIANGLE_FAN | les triangles se touchent et ont en commun le 1er vertex défini. Si on définit 4 vertices, le 1er le 2e et le 3e forment un triangle, le 1er le 3e et le 4e forment un deuxième triangle.
GL_QUADS | les quadruplets de vertices forment des quadrilatères pleins
GL_QUAD_STRIP | tout comme précédemment, les quadrilatères sont connectés, il suffit de 2 nouveaux vertices pour en définir un nouveau, les 2 derniers vertices du quadrilatère précédents étant utilisés
GL_POLYGON | tous les vertices forment un polygone convexe (comme un pentagone ou tout autre polygone ayant plus de 4 sommets)

Une fois le mode de dessin défini, il faut déclarer les vertices avec glVertex :

glVertex2d(-0.75,-0.75);
glVertex2d(0,0.75);
glVertex2d(0.75,-0.75);

Nous l'avons plus haut, une même fonction OpenGL, ici glVertex, peut être appelée avec un nombre variable d'arguments. Ici nous en utilisons 2 (d'où le 2d) donc nous définissions le X et le Y du sommet en question et le Z est automatiquement mis à 0.

Il va sans dire que l'ordre de définition des vertices est important. Il faut suivre le contour du polygone si on ne veut pas se retrouver avec des aberrations.

Pour finir il faut fermer le bloc ouvert par glBegin avec glEnd :

glEnd();

Oublier de fermer un bloc ouvert avec glBegin par glEnd rendra le bloc invalide et rien ne sera dessiné.

Quelques options

Si vous êtes amenés à travailler avec des points ou des lignes, vous aurez sûrement envie d'en changer la taille... OpenGL a pensé à vous :

glPointSize( taille );
glLineWidth( largeur );

taille et largeur sont des réels qui valent 1.0 par défaut.

Comparatif

Pour comprendre l'influence du mode sur le rendu, voici une animation du rendu de 6 vertices avec des modes différents :

Certains modes n'ont pas été utilisés car ils n'avaient que peu d'intérêt dans cet exemple.

Dans le mode GL_QUADS, comme nous avons 6 vertices, les 4 premiers forment un rectangle, mais les 2 autres sont inutilisés.

Couleur et interpolation Fonctions et types

Fonctions et types

Vertices et polygones Les transformations

Fonctions et types

Dans le chapitre précédent nous avons rencontré nos premières fonctions OpenGL.
Par exemple nous avions :

glBegin(GL_TRIANGLES);
glColor3ub(255,0,0);    glVertex2d(-0.75,-0.75);

Pour pouvoir les différencier des autres fonctions, les fonctions OpenGL ont une syntaxe particulière dans leur nom :

glNom[NbType] ( [ Paramètres ] );

J'imagine qu'une petite explication de la formule barbare du dessus n'est pas de refus. ;)

  • gl ou glu : préfixes communs à toutes les fonctions OpenGL ;

  • Nom : il s'agit du nom de la fonction comme Begin ou encore Vertex ;

  • Nb : pour les fonctions à nombre de paramètres variables définit le nombre de paramètres qui vont suivre (ex : pour Begin aucun, pour Vertex 2) ;

  • Type : pour les fonctions à type variable définit le type des paramètres utilisés. Nous utiliserons notamment :

    • i pour integer (entier) ;

    • f pour float (réel) ;

    • d pour double (réel plus précis) ;

    • ub pour unsigned byte (entier entre 0 et 255).

  • Paramètres : pour finir si la fonction a besoin de paramètres il faut les rentrer.

Avec ce formalisme vous comprenez maintenant que glVertex2i est une fonction qui a besoin de deux paramètres de type entier.

Dans nos codes nous serons amenés à rencontrer des constantes OpenGL. On les reconnaît facilement car leur nom est intégralement en majuscules.
Ex : GL_TRUE, GL_FALSE qui sont les équivalents de true, et false (vrai et faux).
Si vous vous rappelez bien, nous sommes déjà passés devant une constante sans nous en rendre compte :

glClear(GL_COLOR_BUFFER_BIT);

Nous disions ici à OpenGL d'effacer le tampon d'affichage en passant en paramètre de glClear une constante qui lui permet de savoir quel tampon effacer.

À tout moment, n'hésitez pas à consulter la documentation pour avoir plus de détails sur l'utilisation d'une fonction.
La documention est disponible sur le site d'OpenGL :

Documentation OpenGL*

* par exemple pour avoir le détail sur glVertex nous cliquons sur gl puis cherchons Vertex.

Vous voyez l'OpenGL ce n'est pas si compliqué !
Dans le prochain chapitre nous restons en 2D pour nous attaquer aux transformations.

Vertices et polygones Les transformations

Les transformations

Fonctions et types Un brin de folie mathématique

J'imagine votre impatience de débuter réellement la 3D. Mon but est de vous y emmener au plus vite mais avec un petit bagage technique nécessaire pour que vous puissiez tout de suite vous amuser et surtout comprendre ce que vous faites.
Ce chapitre vient introduire la notion de transformation, notion que nous pourrons dès à présent appliquer en 2D.

Si vous avez la tête qui tourne lors de la lecture de ce chapitre, c'est normal ! Ce sont les effets de la rotation que nous allons découvrir. ;)

Un brin de folie mathématique

Les transformations Les matrices OpenGL

Un brin de folie mathématique

Les transformations... un bien grand mot. En prenant notre bon vieux bouquin de Maths de 4e ou 3e nous trouvons comme transformations élémentaires :

  • la translation ;

  • la rotation ;

  • la symétrie* ;

  • le changement d'échelle.

* la symétrie centrale équivaut à une rotation de 180° et certaines symétries axiales sont définissables via des changements d'échelle (nous y reviendrons lorsque nous verrons comment réaliser un miroir en OpenGL)

Je ne vais pas m'étaler sur l'explication des actions de ces transformations sur les angles, les longueurs... c'était le boulot de votre prof de collège ! :diable:
Pour ma part je vais vous parler des matrices, car c'est sous cette forme que sont utilisées les transformations en OpenGL (et dans la géométrie spatiale en général).
Les matrices vous connaissez ? Oui euh... non, pas la la matrice ok... c'est pas gagné... Image utilisateur

Une matrice se représente sous la forme d'un tableau de nombres... mais attention son sens et son utilisation vont au-delà de sa représentation. Ce n'est pas qu'un « tableau ».
D'une manière générale une matrice représente une transformation. La matrice définit la manière d'évoluer d'un système.
Ici ce qui nous intéresse c'est l' utilisation géométrique des matrices pour prédire numériquement le résultat d'une transformation.

Je prendrai l'exemple de la rotation 2D car il est facilement compréhensible et représentable. Le principe est le même pour la 3D, avec une dimension en plus bien évidemment.

La rotation d'angle thêta sur le plan (voir dessin ci-dessus), s'écrit matriciellement :

En prenant le cas particulier de la rotation d'angle thêta = 90°, le matrice devient :

Maintenant prenons un vecteur de base, disons le vecteur V de coordonnées V=(1,1).

Appliquons-lui la « transformation » décrite par la matrice. Pour ce faire calculons simplement le produit* matrice x vecteur : V' = M x V

* Un produit matrice x vecteur est assez simple, le dessin ci-dessous l'explique en couleur.

On obtient comme vous le voyez le vecteur V'=(-1,1).

Et en effet en vérifiant graphiquement, le vecteur rouge est bien obtenu par rotation de 90° du vecteur noir comme nous l'avions prévu par le calcul.

Ici nous avons vu comment une matrice pouvait être utilisée pour réaliser une rotation.
En réalité les matrices utilisées sont des matrices 4x4 qui peuvent représenter à la fois une rotation, une translation et une « mise à l'échelle ». Je ne détaillerai pas ici les mathématiques des matrices 3D, mon but étant juste pour l'instant de vous faire comprendre qu'une matrice peut être (et sera) utilisée pour faire des transformations géométriques.

Les transformations Les matrices OpenGL

Les matrices OpenGL

Un brin de folie mathématique Les transformations

Les matrices OpenGL

La plupart du temps, avec OpenGL, nous ne manipulerons pas directement ces matrices mais vous devez savoir qu'il existe trois matrices que nous serons amenés à utiliser via des appels de fonctions simples :

GL_PROJECTION | dans laquelle nous définissons le mode de projection (orthogonale, perspective)
GL_MODELVIEW | pour positionner les objets dans la scène (caméra, vertices, lumières et autres effets). C'est celle que nous manipulerons le plus.
GL_TEXTURE | pour les textures. Nous verrons lors du chapitre sur les textures comment le fait de définir une translation grâce à cette matrice nous permettra de faire des textures animées.
Transformations cumulatives

Comme je vous l'ai dit tout à l'heure en OpenGL on modifie rarement la matrice directement (en affectant des valeurs). On vient plutôt modifier la matrice existante grâce à un appel de fonction. Par exemple si la transformation actuellement stockée dans la matrice est rotation de 90° et qu'on vient demander à faire une rotation de -10°, la matrice contiendra en fait la transformation cumulée c'est-à-dire rotation de 80°.

Pour éviter de cumuler des transformations et repartir à zéro en quelque sorte il faut réinitialiser la matrice (toute analogie avec un film est purement fortuite...).

Réinitialiser une matrice

Avant de faire des modifications sur la matrice de transformation, il faut être sûr de son état de départ. Pour cela on la réinitialise avec la matrice d'identité.*

* La transformée d'un point par la matrice d'identité est lui-même, en gros elle ne transforme rien.

Pour ce faire on utilise la fonction :
glLoadIdentity();

Ainsi nous rajouterons ce morceau de code avant de faire un quelconque dessin :

glMatrixMode( GL_MODELVIEW );
glLoadIdentity( );
Pile de matrices

Vous l'avez compris maintenant, appliquer une transformation en OpenGL revient à multiplier la matrice actuelle par la matrice de notre nouvelle transformation.
Avant d'effectuer une transformation, il faut savoir si nous allons l'appliquer uniquement à un objet, ou à tous ceux qui seront définis par la suite.
En effet si on applique une rotation à un triangle, et qu'on dessine un carré juste après il subira aussi cette transformation.
Deux fonctions permettent d'éviter ça :

  • glPushMatrix( ) : sauvegarde la matrice actuelle ;

  • glPopMatrix( ) : restitue la matrice sauvegardée.

Après un glPushMatrix, on continue à travailler avec l'état actuel de la matrice, on a juste rajouté la possibilité de revenir en arrière.
On peut toujours, à tout moment, recommencer à zéro et réinitialiser la matrice de transformation à la matrice identité : glLoadIdentity();

Quand une matrice est sauvegardée, elle est mise en tête de la pile (de sauvegarde) des matrices.
Un appel à glPopMatrix prend la matrice en tête, l'enlève de la pile et l'utilise comme matrice de transformation actuelle.

La profondeur de cette pile est de 32 matrices pour GL_MODELVIEW, on peut donc faire 32 appels consécutifs à glPushMatrix.

Maintenant que nous savons comment préserver notre matrice, voyons tout de même ce qui nous intéresse ici... la modifier pour appliquer des transformations.

Un brin de folie mathématique Les transformations

Les transformations

Les matrices OpenGL Exercice : une grue

Les transformations

Transformation = changement de repère.
Transformation = changement de repère.
Transformation = changement de repère.
Ah oui au fait, saviez-vous que : transformation = changement de repère ? :p

Je me permets d'insister (lourdement), car c'est ce qu'il faut assimiler pour devenir le roi des transformations.
Dans la vraie vie pour appliquer une transformation à un objet nous le placerions d'abord dans le monde puis nous le ferions tourner par exemple. Ici il faut réfléchir en terme de repère : par des modifications successives de la matrice GL_MODELVIEW nous plaçons, tournons, dimensionnons le repère dans lequel sera ensuite dessiné notre objet.
Ainsi nous appliquons d'abord les transformations que nous voulons, puis au dernier moment nous dessinons notre objet.

J'illustrerai donc les transformations sur la modification apportée au repère. Et pour marquer les esprits sur le fait que les transformations sont accumulées tant que la matrice n'est pas réinitialisée, j'effectuerai les transformations les unes à la suite des autres.

Partons donc du repère de base, ici représenté uniquement dans le plan (X,Y) par souci de simplicité.

Repère de base

Translation

La translation permet de déplacer le repère actuel selon un vecteur V = (x,y,z) ou x, y, et z sont réels. Même si on ne veut déplacer que selon une composante, il faut définir les autres (en les mettant à 0).

glTranslatef ( x , y, z );

Exemple ici :

glTranslated(-2,-1,0);

Donne :

Repère après translation

Rotation

La rotation fait tourner le repère actuel d'un angle thêta (exprimé en degré) selon l'axe défini par le vecteur V = (x,y,z) ou x, y, et z sont des réels :

glRotated ( thêta, x , y, z );

Généralement on ne fait tourner qu'autour d'un des axes principaux (X Y ou Z) à la fois.
Les rotations sur le plan (X,Y) se font autour de l'axe Z, donc pour faire tourner notre repère de 45° il faut faire :

glRotated(45,0,0,1);

Repère après rotation

Je vous l'ai dit tout à l'heure, chaque transformation modifie la matrice. Si on ne revient pas en arrière (remise à zéro, ou restitution d'une matrice sauvegardée), les transformations se combinent. C'est pour ça qu'ici le repère a tourné à partir de la position qui lui avait été donnée après la translation.

Changement d'échelle

Ce n'est pas vraiment une homothétie car on peut changer d'échelle différemment selon les axes.
Elle permet de transformer les axes du repère afin de grossir, diminuer, étirer les objets qui y seront dessinés (« scale » en anglais).
Ainsi si l'on appelle
glScalef ( i, j, k );
le nouveau repère sera tel que x' = i * x, y' = j * y, z' = k * z.

Si l'on souhaite ne pas modifier un axe en particulier (par exemple Z quand on fait de la 2D) il faut mettre 1 et non 0.
Généralement on applique le même facteur à tous les axes pour ne pas déformer, mais rien ne nous empêche de transformer différemment les axes :

glScalef(2,0.5,1);

Repère après changement d'échelle

Importance de l'ordre des transformations

Il est important que vous réfléchissiez à l'ordre dans lequel vous appliquez vos transformations. Par exemple faire une translation suivie d'une rotation n'a pas forcément le même résultat que de faire la rotation puis la translation.

glPushMatrix();
glTranslated(-2,0,0);
glRotated(45,0,0,1);
dessin1(); //triangle rouge
glPopMatrix();
glRotated(45,0,0,1);
glTranslated(-2,0,0);
dessin2(); //triangle bleu

Ordre des transformations

Remarquez au passage l'utilisation qui a été faite de glPushMatrix( ) et glPopMatrix( ), qui nous ont permis, après avoir dessiné le triangle rouge, de revenir au repère de base afin d'entamer les transformations pour le triangle bleu.

Pour être sûr que vous ayez bien saisi les finesses des transformations, rien de tel qu'un exercice pratique et rigolo !

Les matrices OpenGL Exercice : une grue

Exercice : une grue

Les transformations Enfin de la 3D (Partie 1/2)

Exercice : une grue

Je vous propose de vous familiariser avec les transformations par l'intermédiaire d'un petit exercice simple : la construction d'une grue 2D contrôlée par le clavier.

La grue est assez simple et est composée :

  • d'une base ;

  • d'un grand bras ;

  • d'un petit bras ;

  • d'un fil ;

  • d'une caisse.

Je vous conseille de contrôler la grue au clavier et ainsi de pouvoir modifier :

  • l'angle entre le bras et la base ;

  • l'angle entre le petit bras et le grand bras ;

  • la longueur du fil (pour faire monter et descendre la caisse).

Gestion du clavier

Je vous laisse libres du choix des touches. Pour ma part j'ai utilisé les flèches directionnelles : haut/bas pour le fil, gauche/droite pour les bras (shift enfoncé pour le grand bras).

Une réception des événements avec SDL_WaitEvent suffit car on veut ne faire bouger la grue que lors de l'appui sur une touche.
Pensez toutefois à activer la répétition des touches au préalable* :

SDL_EnableKeyRepeat(10,10);

* les valeurs proposées par SDL (SDL_DEFAULT_REPEAT_DELAY et SDL_DEFAULT_REPEAT_INTERVAL) sont trop lentes. Avec 10, 10 vous aurez un mouvement plus fluide.

Pour avoir quelque chose d'un chouilla réaliste, je vous conseille de limiter la plage de valeurs que peuvent prendre vos variables (angles et longueur). J'utilise :

  • angle grand bras/base entre 10° et 90° ;

  • angle petit bras/grand bras entre -90° et 90° ;

  • longueur entre 10 et 100.

Dessin de la grue

Nous restons encore en 2D mais pour faciliter les choses il serait bien de pouvoir avoir des coordonnées de l'ordre des pixels. Pour ce faire nous allons modifier la matrice de projection pour faire de la projection 2D dont nous spécifierons cette fois les dimensions (alors qu'elles étaient par défaut entre -1 et 1 au préalable).

SDL_WM_SetCaption("Exercice : une grue", NULL);
SDL_SetVideoMode(LARGEUR_ECRAN, HAUTEUR_ECRAN, 32, SDL_OPENGL);
glMatrixMode( GL_PROJECTION );
glLoadIdentity( );
gluOrtho2D(0,LARGEUR_ECRAN,0,HAUTEUR_ECRAN);

De cette manière nous aurons l'espace de coordonnées suivant :

Enfin dernier conseil avant de vous lâcher dans la nature, il peut être utile à tout moment de savoir où est le repère actuel et comment il est orienté. Pour ce faire voici une petite fonction que vous pouvez appeler n'importe quand dans votre dessin pour « déboguer » et mieux visualiser vos transformations.

/*
    Dessine le repère actuel pour faciliter
    la compréhension des transformations.
    Utiliser « echelle » pour avoir un repère bien orienté et positionné
    mais avec une échelle différente.
*/
void dessinerRepere(unsigned int echelle = 1)
{
    glPushMatrix();
    glScalef(echelle,echelle,echelle);
    glBegin(GL_LINES);
    glColor3ub(0,0,255);
    glVertex2i(0,0);
    glVertex2i(1,0);
    glColor3ub(0,255,0);
    glVertex2i(0,0);
    glVertex2i(0,1);
    glEnd();
    glPopMatrix();
}

Par exemple, après avoir dessiné ma base si j'appelle

dessinerRepere(50);

j'obtiens :

Je vois donc ici que je suis prêt à faire la première rotation et dessiner le grand bras.

En ce qui concerne le fil, il doit toujours être à la verticale. Il faut donc que vous trouviez, une fois au bout du petit bras, un moyen d'annuler les rotations pour remettre le repère « à l'endroit ».

Bon courage !

Correction

Je vous donne ma version de la grue. Ce n'est qu'un guide rien ne vous oblige à faire « exactement » pareil.

#include <SDL/SDL.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <cstdlib>
#define LARGEUR_BASE 50
#define HAUTEUR_BASE 20
#define LARGEUR_BRAS_1 150
#define HAUTEUR_BRAS_1 15
#define LARGEUR_BRAS_2 50
#define HAUTEUR_BRAS_2 10
#define TAILLE_CAISSE 10
#define LARGEUR_ECRAN (LARGEUR_BASE + LARGEUR_BRAS_1 + HAUTEUR_BRAS_2 + 50)
#define HAUTEUR_ECRAN (HAUTEUR_BASE + LARGEUR_BRAS_1 + HAUTEUR_BRAS_2 + 50)
int angle1 = 45;
int angle2 = -20;
int longueur = 50;
void Dessiner();
int main(int argc, char *argv[])
{
    SDL_Event event;
    SDL_Init(SDL_INIT_VIDEO);
    atexit(SDL_Quit);
    SDL_WM_SetCaption("Exercice : une grue", NULL);
    SDL_SetVideoMode(LARGEUR_ECRAN, HAUTEUR_ECRAN, 32, SDL_OPENGL);
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity( );
    gluOrtho2D(0,LARGEUR_ECRAN,0,HAUTEUR_ECRAN);
    SDL_EnableKeyRepeat(10,10);
    Dessiner();
    while(SDL_WaitEvent(&event))
    {
        switch(event.type)
        {
            case SDL_QUIT:
            exit(0);
            break;
            case SDL_KEYDOWN:
            switch (event.key.keysym.sym)
            {
                case SDLK_UP:
                longueur --;
                if (longueur < 10)
                    longueur = 10;
                break;
                case SDLK_DOWN:
                longueur ++;
                if (longueur > 100)
                    longueur = 100;
                break;
                case SDLK_LEFT:
                if ((event.key.keysym.mod & KMOD_LSHIFT) == KMOD_LSHIFT)
                {
                    angle1++;
                    if (angle1 > 90)
                        angle1 = 90;
                }
                else
                {
                    angle2++;
                    if (angle2 > 90)
                        angle2 = 90;
                }
                break;
                case SDLK_RIGHT:
                if ((event.key.keysym.mod & KMOD_LSHIFT) == KMOD_LSHIFT)
                {
                    angle1--;
                    if (angle1 < 10)
                        angle1 = 10;
                }
                else
                {
                    angle2--;
                    if (angle2 < -90)
                        angle2 = -90;
                }
                break;
            }
            break;
        }
        Dessiner();
    }
    return 0;
}
/*
    Dessine un rectangle avec comme point de référence
    le milieu du côté gauche
*/
void dessineRectangle(double largeur,double hauteur)
{
    glBegin(GL_QUADS);
    glVertex2d(0,-hauteur/2);
    glVertex2d(0,hauteur/2);
    glVertex2d(largeur,hauteur/2);
    glVertex2d(largeur,-hauteur/2);
    glEnd();
}
/*
    Dessine le repère actuel pour faciliter
    la compréhension des transformations.
    Utiliser "echelle" pour avoir un repère bien orienté et positionné
    mais avec une échelle différente.
*/
void dessinerRepere(unsigned int echelle = 1)
{
    glPushMatrix();
    glScalef(echelle,echelle,echelle);
    glBegin(GL_LINES);
    glColor3ub(0,0,255);
    glVertex2i(0,0);
    glVertex2i(1,0);
    glColor3ub(0,255,0);
    glVertex2i(0,0);
    glVertex2i(0,1);
    glEnd();
    glPopMatrix();
}
void Dessiner()
{
    glClear( GL_COLOR_BUFFER_BIT );
    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity( );
    /* Je déplace mon répère initial (actuellement
    en bas à gauche de l'écran) */
    glTranslated(LARGEUR_BASE/2,HAUTEUR_BASE,0);
    // La base
    glColor3ub(254,128,1);
    dessineRectangle(LARGEUR_BASE,HAUTEUR_BASE);
    //Je me place en haut au milieu de la base
    glTranslated(LARGEUR_BASE/2,HAUTEUR_BASE/2,0);
    // Le grand bras
    glRotated(angle1,0,0,1);
    glColor3ub(248,230,7);
    dessineRectangle(LARGEUR_BRAS_1,HAUTEUR_BRAS_1);
    // Je me place au bout du grand bras
    glTranslated(LARGEUR_BRAS_1,0,0);
    // Puis m'occupe du petit bras
    glRotated(angle2,0,0,1);
    glColor3ub(186,234,21);
    dessineRectangle(LARGEUR_BRAS_2,HAUTEUR_BRAS_2);
    // Je me place au bout du petit bras
    glTranslated(LARGEUR_BRAS_2,0,0);
    /* J'annule les rotations pour avoir mon repère aligné
    avec le repère d'origine */
    glRotated(-angle1-angle2,0,0,1);
    // Je dessine le fil
    glColor3ub(255,255,255);
    glBegin(GL_LINES);
    glVertex2i(0,0);
    glVertex2i(0,-longueur);
    glEnd();
    /* Je descends en bas du fil (avec un petit décalage
    sur X pour anticiper le dessin de la caisse */
    glTranslated(-TAILLE_CAISSE/2,-longueur,0);
    // Et je dessine enfin la caisse
    glColor3ub(175,175,85);
    dessineRectangle(TAILLE_CAISSE,TAILLE_CAISSE);
    glFlush();
    SDL_GL_SwapBuffers();
}

Téléchargez le projet Code::Blocks, l'exécutable Windows et le Makefile Unix (118 Ko)

Voilà, si vous avez fait l'exercice proposé vous êtes maintenant les rois des transformations OpenGL.
Nous allons enfin pouvoir nous attaquer à ce que nous attendons tant : la 3D.
Direction le prochain chapitre pour voir comment paramétrer et utiliser la caméra.

Les transformations Enfin de la 3D (Partie 1/2)

Enfin de la 3D (Partie 1/2)

Exercice : une grue Du réel à l'écran

Enfin on y est ! On va pouvoir passer à la 3D !
Pour faire ce passage en douceur je scinde ce chapitre en deux parties.
Nous commencerons donc par comprendre comment il est possible de faire de la 3D sur un écran 2D, ce qu'il nous faut préparer pour « dessiner » en 3D, et dans la seconde partie nous dessinerons un cube et verrons les problèmes que cela pose.

Du réel à l'écran

Enfin de la 3D (Partie 1/2) La perspective

Du réel à l'écran

Quand vous décrivez votre scène 3D en OpenGL (à coup de glVertex), vous décrivez le monde tel qu'il est dans l'absolu, dans son propre repère.
Pour passer du monde réel à l'écran, il faut donner quelques indications à OpenGL :

  • définir quelle zone de la fenêtre servira pour le rendu* ;

  • définir le mode de projection (perspective par exemple) ;

  • placer la caméra dans le monde réel.

* Cette phase est automatiquement réalisée à la création de la fenêtre. Nous verrons dans la partie II comment la changer pour faire du split-screen (plusieurs écrans de rendu sur la même fenêtre).

Grâce à ces informations, OpenGL pourra déterminer les transformations à faire subir aux objets du monde réel à 3 dimensions pour les dessiner sur l'écran à 2 dimensions.

Dans le monde réel on travaille au niveau des vertices, et sur l'écran au niveau des pixels. Sur le dessin du haut vous avez peut-être remarqué que je n'ai pas fait apparaître de Z écran. C'était juste pour ne pas vous embrouiller car l'écran est bien à 2 dimensions. Mais le Z écran sert tout de même à donner une information de profondeur des pixels.

Le repère X,Y,Z de l'écran n'est pas direct : si on applique la règle de la main droite. L'axe Z devrait aller de l'écran vers nous. Cependant la convention a été prise en synthèse d'image que l'axe Z s'enfonce dans l'écran.

Quelle est l'unité de distance en OpenGL ?

Réponse : celle que vous voulez ! En effet quand nous y pensons qu'est-ce qui fait que quelque chose est grand ou petit en réalité ? Sans référentiel il nous est difficile de déterminer visuellement la taille d'un objet. Nous savons qu'il est petit car il y a un autre objet à côté dont nous connaissons la taille. Nous savons comparer.
Comment savoir que quelque chose est loin ? Parce que quand nous bougeons, sa taille ne varie pas beaucoup et parce que nous mettons du temps à nous en approcher.

Ainsi en OpenGL se dire que « 1 » est 1 mètre comme 1 millimètre ou comme 1 kilomètre revient au même du moment que les proportions sont gardées et que les vitesses des mouvements sont adaptées. En effet si on fait face à un cube de « 1 » de largeur et qu'appuyer une fois sur la touche « avancer » nous fait bouger de 10000 on se dira : soit je vais très vite, soit l'objet était (car on vient de le perdre) très très petit en fait.

Cependant il est parfois utile de se rapporter à des unités connues. Si 1 unité OpenGL est 1 mètre, alors il sera facile d'utiliser un moteur physique qui utilise des vraies unités.

Enfin de la 3D (Partie 1/2) La perspective

La perspective

Du réel à l'écran Placer la caméra

La perspective

Le passage le plus important dans la 3D est la projection. Pour passer d'un monde décrit en 3D à une fenêtre avec des pixels 2D il nous faut perdre une dimension et donc projeter.

La méthode de projection que nous utiliserons en 3D est la perspective. La perspective est définie par la pyramide ci-dessous :

pyramide de clipping

Cette pyramide s'appelle la pyramide de clipping. C'est-à-dire que tout objet ne se trouvant pas à l'intérieur de la zone bleue ne sera pas dessiné. De plus sa forme permet de définir comment projeter les objets sur l'écran en faisant un parallèle avec la pyramide réelle qui a pour sommet l'oeil de l'utilisateur et coupe son écran :

pyramide réelle (ici vue du dessus)

Le ratio (vu dans le schéma plus haut) est un rapport entre la largeur et la hauteur. Pour une télévision ce ratio vaut 4/3, des fois 16/9. Pour les tailles informatiques standards (1024x768, 800x600, 640x480) il est aussi de 4/3. Mais votre fenêtre n'est pas obligée d'utiliser tout l'écran et peut donc avoir ses propres proportions et il existe aussi de plus en plus d'écrans avec des ratios spéciaux. Il est donc important de ne pas le prendre comme acquis dans vos applications et toujours le définir en fonction de la taille de la fenêtre que vous créez (je ferai une annexe SDL sur comment détecter les modes disponibles pour le plein écran).

Les paramètres near et far déterminent les distances minimales et maximales des objets. En dehors de cet intervalle les objets ne seront pas affichés. Cela posera parfois problème si la valeur de far est trop petite, l'utilisateur risque de voir disparaître des objets quand il s'éloigne, ou pire voir la scène apparaître subitement. Nous verrons dans la partie II comment utiliser le brouillard pour parer à ce problème. Pour l'instant une valeur grande (vis-à-vis de la taille de votre scène) suffira amplement.

Le seul paramètre qui peut vous échapper ici est l'angle. Il s'agit de l'angle de vision entre les plans haut et bas de la pyramide (souvent dénoté fovy pour field of view sur l'axe y). Généralement on utilise une valeur aux alentours de 70°. Lors de la sortie d' Half-Life 2 il y a eu toute une polémique sur l'angle de vue mal choisi (90°) qui rendait certaines personnes mal à l'aise.
Pour voir l'influence de cet angle, je vous ai fait 3 rendus d'une même scène avec des angles différents :

On constate que si l'on donne un angle petit , ça donne un effet de zoom . Et je ne vous cacherai pas que c'est exactement ce qui est utilisé dans les jeux pour faire un zoom (jumelles ou sniper).

Application en OpenGL

Pour définir la perspective comme mode de projection il suffit d'appeler la fonction

gluPerspective( fovy, ratio, near, far );

Par exemple pour une fenêtre de 640x480 :

gluPerspective(70,(double)640/480,1,1000);

Cet appel modifie la matrice courante, même s'il ne s'agit pas de la matrice de projection. Il faut donc bien veiller à sélectionner la matrice GL_PROJECTION, l'initialiser et ensuite appeler gluPerspective.

Voici donc notre nouveau code d'initialisation de notre application OpenGL :

SDL_Init(SDL_INIT_VIDEO);
SDL_WM_SetCaption("SDL GL Application", NULL);
SDL_SetVideoMode(640, 480, 32, SDL_OPENGL);
glMatrixMode( GL_PROJECTION );
glLoadIdentity( );
gluPerspective(70,(double)640/480,1,1000);

Comme la définition de la perspective ne concerne que la matrice GL_PROJECTION nous pouvons nous contenter de l'initialiser une seule fois et non pas à chaque image. Bien sûr si le ratio de la fenêtre venait à changer dynamiquement il faudrait redéfinir la perspective au risque de voir tous les objets déformés.

Du réel à l'écran Placer la caméra

Placer la caméra

La perspective Enfin de la 3D (Partie 2/2)

Placer la caméra

Maintenant que nous savons comment projeter, il serait bien de pouvoir placer le point de vue n'importe où dans la scène. Pour cela nous utilisons une sorte de caméra virtuelle avec l'appel de :

gluLookAt( camX, camY, camZ, cibleX, cibleY, cibleZ, vertX, vertY, vertZ );

camX, camY et camZ définissent la position de la caméra ;
cibleX, cibleY et cibleZ définissent la position du point que fixe la caméra (le point correspondant se trouvera au centre de la fenêtre d'affichage) ;
vertX, vertY et vertZ définissent le vecteur vertical.

Voici 3 images prises avec des paramètres différents de la caméra pour que vous compreniez l'utilité de chaque paramètre :

gluLookAt(1.5, 1.5, 5, 0, 0, 0, 0, 1, 0);
La caméra est en (1.5,1.5,5) et regarde en (0,0,0).

gluLookAt(1.5, 1.5, 5, 1.5, 1.5, 0, 0, 1, 0);
La caméra est en (1.5,1.5,5) et regarde en (1.5,1.5,0) c'est-à-dire droit devant elle (contrairement à l'image précédente).

gluLookAt(1.5, 1.5, 5, 0, 0, 0, 1, 1, 0);
Même position et regard que précédemment mais son vecteur vertical est (1,1,0), or la scène a été pensée avec une verticale de (0,1,0), l'image est donc « penchée » sur le côté.

L'importance de la verticale

Vous l'avez vu, l'appel à gluLookAt vous permet de définir la verticale que vous voulez pour votre scène. Le tout est d'être cohérent et de concevoir votre scène en conséquence.
Il est néanmoins beaucoup plus pratique de choisir comme verticale un vecteur du repère de base X (1,0,0), Y (0,1,0) ou Z (0,0,1).

Entre Y et Z lequel est le meilleur choix ?

Bonne question. L'un ou l'autre sont des choix valables : on peut imaginer le passage à la 3e dimension comme l'ajout de la profondeur tout comme l'ajout de la hauteur. Dans le monde de la synthèse d'image il n'est pas rare de voir l'un ou l'autre.

À l'avenir, j'essayerai de respecter le choix verticale = Z car il a été fait dans de nombreux jeux et OpenGL semble l'avoir favorisé (nous le verrons lors du chapitre sur les quadriques...).

Éditeur de FarCry, Z est la verticale

Quand l'appeler ?

Placer la caméra revient à déplacer tout le monde pour qu'il soit centré sur la caméra et orienté selon l'axe du regard. Cela influe donc logiquement sur la matrice GL_MODELVIEW et vous ne serez pas étonnés donc que l'appel de la caméra soit juste après la réinitialisation de GL_MODELVIEW.

void Dessiner()
{
    glClear( GL_COLOR_BUFFER_BIT );
    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity( );
    gluLookAt(3,3,3,0,0,0,0,0,1); //exemple
    /* Dessin 3D ici */
    glFlush();
    SDL_GL_SwapBuffers();
}

Voilà vous savez maintenant comment il est possible de passer d'un monde 3D à un écran 2D et savez placer la caméra.
Pour récapituler, notre nouveau squelette de programme pour dessiner de la 3D :

#include <SDL/SDL.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <cstdlib>
void Dessiner();
int main(int argc, char *argv[])
{
    SDL_Event event;
    SDL_Init(SDL_INIT_VIDEO);
    atexit(SDL_Quit);
    SDL_WM_SetCaption("SDL GL Application", NULL);
    SDL_SetVideoMode(640, 480, 32, SDL_OPENGL);
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    gluPerspective(70,(double)640/480,1,1000);
    Dessiner();
    for (;;)
    {
        SDL_WaitEvent(&event);
        switch(event.type)
        {
            case SDL_QUIT:
            exit(0);
            break;
        }
        Dessiner();
    }
    return 0;
}
void Dessiner()
{
    glClear( GL_COLOR_BUFFER_BIT );
    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity( );
    gluLookAt(3,3,3,0,0,0,0,0,1);
    /* Dessin 3D */
    glFlush();
    SDL_GL_SwapBuffers();
}

Maintenant il ne reste plus qu'à remplir la partie dessin. Et pour ça direction le chapitre suivant avec notre premier dessin 3D : un cube !

La perspective Enfin de la 3D (Partie 2/2)

Enfin de la 3D (Partie 2/2)

Placer la caméra Un cube

Dans le dernier chapitre nous nous sommes préparés à passer à la 3D, il est donc temps de s'y mettre et d'entamer notre premier dessin : un cube ! En dessinant notre cube et en l'animant, nous rencontrerons des problèmes, prévus d'avance rassurez-vous ^^ , et nous verrons comment les résoudre.

Un cube

Enfin de la 3D (Partie 2/2) Le Z-Buffer

Un cube

L'exemple du cube est assez simple et nous continuerons avec lors de la création de notre première scène texturée.

Voyons tout d'abord comment est constitué un cube :

Coordonnées des sommets du cube

Un cube est composé de 8 sommets et 6 faces, chaque face faisant intervenir 4 sommets.
Nous n'allons pas nous contenter de dessiner chacun des 8 sommets, nous n'aurions pas de faces pleines. Il nous faut donc décrire les faces une par une, en indiquant les sommets qu'elles font intervenir.

Décrire des sommets en 3D

Ici nous devons définir 3 coordonnées pour chaque sommet : X, Y et Z. Nous ne pouvons donc plus utiliser le basique glVertex2d que nous utilisions auparavant. Il va falloir donc utiliser la version avec 3 arguments soit glVertex3d.

Exemple :

glVertex3d(1,1,1);

Le 3d de glVertex3d ne veut pas dire « ouais je fais de la 3D ! ». Rappelez-vous le chapitre Notions de base, 3 spécifie le nombre d'arguments et d le fait que les arguments donnés soient des double (réels).

Décrire le cube

Les faces étant des carrés, nous allons utiliser le mode GL_QUADS pour décrire les vertices.
Pour différencier les faces nous leur attribuerons une couleur différente ; nous ferons la première (celle avec les flèches) en rouge. En ce qui concerne la caméra, j'ai choisi de la placer en (3,4,2) pour regarder le cube centré en (0,0,0), car cela donnera un bon angle de vue (c'est la position utilisée pour le schéma plus haut).

Ce qui donne donc :

void Dessiner()
{
    glClear( GL_COLOR_BUFFER_BIT );
    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity( );
    gluLookAt(3,4,2,0,0,0,0,0,1);
    glBegin(GL_QUADS);
    glColor3ub(255,0,0); //face rouge
    glVertex3d(1,1,1);
    glVertex3d(1,1,-1);
    glVertex3d(-1,1,-1);
    glVertex3d(-1,1,1);
    glEnd();
    glFlush();
    SDL_GL_SwapBuffers();
}

Facile ! Il suffit de choisir un point de départ et de suivre le contour de la face pour décrire les sommets un par un.

Je vous conseille, pour les décrire, de vous imaginer faisant face à la face (hum... Image utilisateur ) et de les énumérer un à un dans le sens inverse des aiguilles d'une montre. En effet nous verrons plus tard que cela nous sera utile quand nous voudrons éviter de dessiner les faces cachées. Encore une fois quelque soit le sens que vous décidez d'utiliser, le tout est d'être cohérents et de garder le même sens.

Continuons donc avec la 2e face, disons celle à gauche de la première (quand on regarde le schéma) et faisons-la en vert.

glColor3ub(0,255,0); //face verte
    glVertex3d(1,-1,1);
    glVertex3d(1,-1,-1);
    glVertex3d(1,1,-1);
    glVertex3d(1,1,1);

Ce qui me donne le résultat suivant :

Bon maintenant parce que je veux vous montrer un problème important, attaquons-nous à la face de derrière que nous ferons... devinez... en bleu ! ;)

Rien de compliqué, il suffit de suivre le schéma de tout à l'heure pour avoir rapidement les coordonnées et écrire le code approprié.

glColor3ub(0,0,255); //face bleue
    glVertex3d(-1,-1,1);
    glVertex3d(-1,-1,-1);
    glVertex3d(1,-1,-1);
    glVertex3d(1,-1,1);

Et voilà le résultat :

o_O o_O o_O

En effet vous ne rêvez pas, la face bleue qui était censée être derrière, donc en majeure partie cachée par la rouge et la verte vient se dessiner par-dessus ces dernières.
Et c'est tout à fait logique, OpenGL dessine les carrés dans l'ordre dans lequel on les définit . Il ne se soucie pour l'instant pas de savoir s'il y a déjà quelque chose là où il dessine et vient donc écraser les faces précédentes.

La solution ? Le Z-Buffer !

Enfin de la 3D (Partie 2/2) Le Z-Buffer

Le Z-Buffer

Un cube Animation

Le Z-Buffer

Le Z-Buffer ou Depth-Buffer (pour tampon de profondeur) sert à éviter le problème que nous venons de rencontrer.

Principe du Z-Buffer

Le Z-Buffer est un tampon (buffer) qui stocke la profondeur (d'où le Z, X et Y sur l'écran étant la position en pixel) de chaque pixel affiché à l'écran.
Ensuite quand OpenGL demande à dessiner un pixel à un endroit, il compare la profondeur du point à afficher et celle présente dans le buffer. Si le nouveau pixel est situé devant l'ancien, alors il est dessiné et la valeur de la profondeur dans le buffer est mise à jour. Sinon, le pixel était alors situé derrière et n'a donc pas lieu d'être affiché.

Pour bien comprendre, suivons le cheminement qui est fait.

  • Au départ le buffer (ici de taille 3x3 pour l'exemple) est initialisé à des distances infinies (le plus loin possible vers le fond de votre écran).

  • OpenGL demande à dessiner un pixel en (2,2,5).
    Valeur demandée : 5.
    Valeur présente : infini.
    5 < infini => OK pour dessin
    Dessin du pixel + Mise à jour du Z-buffer avec la valeur 5.

  • Demande de dessin d'un pixel en (2,2,10).
    Valeur demandée : 10.
    Valeur présente : 5.
    10 > 5 => Dessin refusé

Application dans OpenGL

Heureusement pour nous OpenGL gère très bien cette technique , il nous faut juste modifier notre programme pour l'activer et bien l'utiliser !

Pour cela il nous faut :

  • activer son utilisation : après la création de la fenêtre OpenGL il faut simplement appeler :

    glEnable(GL_DEPTH_TEST);

  • le réinitialiser à chaque nouvelle image, en même temps que le buffer des pixels :

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) ;

Ce qui nous donne un code complet (avec le début de notre cube) :

#include <SDL/SDL.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <cstdlib>
void Dessiner();
int main(int argc, char *argv[])
{
    SDL_Event event;
    SDL_Init(SDL_INIT_VIDEO);
    atexit(SDL_Quit);
    SDL_WM_SetCaption("SDL GL Application", NULL);
    SDL_SetVideoMode(640, 480, 32, SDL_OPENGL);
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    gluPerspective(70,(double)640/480,1,1000);
    glEnable(GL_DEPTH_TEST);
    Dessiner();
    for (;;)
    {
        SDL_WaitEvent(&event);
        switch(event.type)
        {
            case SDL_QUIT:
            exit(0);
            break;
        }
        Dessiner();
    }
    return 0;
}
void Dessiner()
{
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity( );
    gluLookAt(3,4,2,0,0,0,0,0,1);
    glBegin(GL_QUADS);
    glColor3ub(255,0,0); //face rouge
    glVertex3d(1,1,1);
    glVertex3d(1,1,-1);
    glVertex3d(-1,1,-1);
    glVertex3d(-1,1,1);
    glColor3ub(0,255,0); //face verte
    glVertex3d(1,-1,1);
    glVertex3d(1,-1,-1);
    glVertex3d(1,1,-1);
    glVertex3d(1,1,1);
    glColor3ub(0,0,255); //face bleue
    glVertex3d(-1,-1,1);
    glVertex3d(-1,-1,-1);
    glVertex3d(1,-1,-1);
    glVertex3d(1,-1,1);
    glEnd();
    glFlush();
    SDL_GL_SwapBuffers();
}

Et en effet en exécutant notre nouveau code nous obtenons ceci :

Ouf ! Nous avons enfin ce que nous désirions. Bien pratique ce z-buffer !

Finir le cube

À ce stade il ne vous reste plus qu'à compléter le code avec les 3 faces restantes et leur choisir de belles couleurs.
N'oubliez pas que vous pouvez utiliser le schéma du début pour facilement trouver les coordonnées des sommets de la face en cours.

Voici mon code pour ceux qui ne veulent pas essayer eux-mêmes, ou simplement pour comparer :

glBegin(GL_QUADS);
    glColor3ub(255,0,0); //face rouge
    glVertex3d(1,1,1);
    glVertex3d(1,1,-1);
    glVertex3d(-1,1,-1);
    glVertex3d(-1,1,1);
    glColor3ub(0,255,0); //face verte
    glVertex3d(1,-1,1);
    glVertex3d(1,-1,-1);
    glVertex3d(1,1,-1);
    glVertex3d(1,1,1);
    glColor3ub(0,0,255); //face bleue
    glVertex3d(-1,-1,1);
    glVertex3d(-1,-1,-1);
    glVertex3d(1,-1,-1);
    glVertex3d(1,-1,1);
    glColor3ub(255,255,0); //face jaune
    glVertex3d(-1,1,1);
    glVertex3d(-1,1,-1);
    glVertex3d(-1,-1,-1);
    glVertex3d(-1,-1,1);
    glColor3ub(0,255,255); //face cyan
    glVertex3d(1,1,-1);
    glVertex3d(1,-1,-1);
    glVertex3d(-1,-1,-1);
    glVertex3d(-1,1,-1);
    glColor3ub(255,0,255); //face magenta
    glVertex3d(1,-1,1);
    glVertex3d(1,1,1);
    glVertex3d(-1,1,1);
    glVertex3d(-1,-1,1);
    glEnd();

Et le résultat graphique correspondant :

Comme vous avez le code sous les yeux vous savez que vous n'avez pas triché et que le code fait bien un cube 3D. Mais personnellement je vois 3 quadrilatères de couleur, je peux faire pareil sous Paint en 30 secondes, la preuve :
Ok c'est moche et mal fait mais avec un peu de soin j'aurais pu avoir pareil ! :honte:

Il est donc temps de profiter de la puissance de la 3D temps réel et d'animer notre cube pour le voir sous toutes les coutures !

Un cube Animation

Animation

Le Z-Buffer Les textures

Animation

Pour animer notre cube nous allons simplement le faire tourner en utilisant ce que vous connaissez déjà par coeur : la rotation !

Au niveau du dessin nous n'avons vraiment pas grand chose à changer, juste à faire tourner le repère avant de dessiner le cube. Nous allons le faire tourner à la fois sur Z (la verticale) et X donc nous avons besoin de 2 variables globales* pour chacun des angles à contrôler.

* En général en programmation on essaye d'éviter les variables globales mais ici nous faisons en quelque sorte du prototypage pour apprendre et tester les concepts OpenGL, ce n'est donc vraiment pas bien grave.

Le code, simplifié, du programme devient alors :

#include <SDL/SDL.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <cstdlib>
void Dessiner();
double angleZ = 0;
double angleX = 0;
int main(int argc, char *argv[])
{
   //le code du main
}
void Dessiner()
{
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity( );
    gluLookAt(3,4,2,0,0,0,0,0,1);
    glRotated(angleZ,0,0,1);
    glRotated(angleX,1,0,0);
    //dessin du cube
    glFlush();
    SDL_GL_SwapBuffers();
}

En terme de code SDL nous voulons que ces angles soient modifiés automatiquement avec le temps. On ne peut donc plus se permettre d' attendre les événements avec SDL_WaitEvent et nous allons en conséquence utiliser SDL_PollEvent pour récupérer les événements s'il y en a puis animer notre cube.

Nous modifions donc le code de notre boucle d'affichage pour incrémenter nos angles à chaque image :

for (;;)
    {
        while (SDL_PollEvent(&event))
        {
            switch(event.type)
            {
                case SDL_QUIT:
                exit(0);
                break;
            }
        }
        angleZ += 1;
        angleX += 1;
        Dessiner();
    }
Gérer la vitesse d'animation

En testant le code actuel vous voyez que le cube tourne beaucoup trop vite, nous n'avons franchement rien le temps de voir.
Il faut donc introduire des vitesses de rotation. Ces vitesses ne doivent pas dépendre de l'ordinateur sur lequel le programme est lancé et donc doivent prendre en compte le temps réel.
Pour ce faire, à chaque image (chaque passage dans la boucle donne lieu à une image), il faut déterminer combien de temps il s'est passé depuis la dernière image et faire bouger le cube en conséquence.

Nous avons donc besoin de 3 variables :

  • une pour garder en mémoire le temps qu'il était lors de la dernière image : last_time ;

  • une pour avoir le temps de l'image actuelle : current_time ;

  • une (par commodité) pour le temps écoulé : ellapsed_time.

Pour connaître le temps écoulé, en millisecondes, depuis le lancement de l'application nous utiliserons l'instruction SDL_GetTicks();

Le principe est alors le suivant.

  1. On initialise une première fois , avant de rentrer dans notre boucle d'affichage last_time avec le temps actuel.

  2. À chaque image on récupère le temps actuel dans current_time.

  3. On utilise la différence entre le temps actuel et le temps qu'il était lors de l'ancien passage pour savoir combien de temps s'est écoulé. On stocke le résultat dans ellapsed_time.

  4. On réalise nos mouvements en fonction du temps écoulé.

  5. On finit par affecter à last_time la valeur de current_time car nous passons à une nouvelle image et donc le présent devient du passé ( :'( c'est beau !)

    6. .

En ce qui concerne l'unité de mesure des vitesses, comme le temps écoulé est donné en millisecondes et que les angles utilisés dans glRotate sont en degrés , il s'agit tout simplement de degrés par milliseconde.

Dans notre cas j'utiliserai 0.05 °/ms.

La traduction en code du principe tout juste évoqué est la suivante :

Uint32 last_time = SDL_GetTicks();
    Uint32 current_time,ellapsed_time;
    for (;;)
    {
        while (SDL_PollEvent(&event))
        {
            switch(event.type)
            {
                case SDL_QUIT:
                exit(0);
                break;
            }
        }
        current_time = SDL_GetTicks();
        ellapsed_time = current_time - last_time;
        last_time = current_time;
        angleZ += 0.05 * ellapsed_time;
        angleX += 0.05 * ellapsed_time;
        Dessiner();
    }
Ne pas monopoliser le CPU

Ça rame ! Le programme prend 100% du CPU rien que pour faire tourner un simple cube, je ne vais jamais pouvoir faire un jeu !

En effet si on regarde la charge du processeur imposée par notre application on voit qu'il est totalement occupé à gérer notre programme :

Cela ne veut pas dire que votre programme est lent c'est juste que nous bouclons en permanence et que nous ne prenons jamais de pause. En réalité nous n'avons pas vraiment besoin de boucler tout le temps. Nous allons utiliser une technique possible (celle que je préfère et donc souhaite vous expliquer) : limiter les FPS (frames per second - images par seconde).
En effet pour avoir une animation très fluide il nous suffit de 50 images par seconde.

En fixant le nombre d'images par secondes désirées par votre application, il est alors possible de la faire s'endormir un certain temps si elle va plus vite que nécessaire, ce qui soulagera (même s'il ne s'en plaint pas) le processeur.
Pour ce faire nous allons calculer à chaque image combien de temps nous avons mis pour la dessiner, si nous avons été plus rapides que le temps moyen nécessaire, nous stopperons l'exécution pour un certain temps.

Par exemple, autoriser 50 images par seconde donne à chaque image 20 millisecondes pour s'afficher. Imaginons qu'une image mette 5 ms à s'afficher réellement, il reste alors 15 ms à tuer. Plutôt que de passer directement à l'image suivante, nous allons endormir l'application pendant ces 15 ms.

En terme de code nous allons utiliser SDL_GetTicks comme auparavant pour déterminer le temps écoulé entre le début et la fin de la création de l'image. Nous utiliserons SDL_Delay pour suspendre temporairement l'application.

Uint32 start_time; //nouvelle variable
    for (;;)
    {
        start_time = SDL_GetTicks();
        while (SDL_PollEvent(&event))
        {
            switch(event.type)
            {
                case SDL_QUIT:
                exit(0);
                break;
                case SDL_KEYDOWN:
                animation = !animation;
                break;
            }
        }
        current_time = SDL_GetTicks();
        ellapsed_time = current_time - last_time;
        last_time = current_time;
        angleZ += 0.05 * ellapsed_time;
        angleX += 0.05 * ellapsed_time;
        Dessiner();
        ellapsed_time = SDL_GetTicks() - start_time;
        if (ellapsed_time < 10)
        {
            SDL_Delay(10 - ellapsed_time);
        }
    }
    return 0;
}

Notez qu'ici nous ne voulons pas savoir combien de temps il s'est passé depuis la dernière fois mais combien de temps notre image a pris à se dessiner (j'y ai inclus la gestion des événements). Il y a donc un appel à SDL_GetTicks au début de notre boucle et un appel à la toute fin. Je réutilise ellapsed_time par commodité mais pas les autres variables pour ne pas mélanger les 2 concepts : limitation des FPS et gestion du temps dans les animations.

Code final :

#include <SDL/SDL.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <cstdlib>
void Dessiner();
double angleZ = 0;
double angleX = 0;
int main(int argc, char *argv[])
{
    SDL_Event event;
    SDL_Init(SDL_INIT_VIDEO);
    atexit(SDL_Quit);
    SDL_WM_SetCaption("SDL GL Application", NULL);
    SDL_SetVideoMode(640, 480, 32, SDL_OPENGL);
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    gluPerspective(70,(double)640/480,1,1000);
    glEnable(GL_DEPTH_TEST);
    Dessiner();
    Uint32 last_time = SDL_GetTicks();
    Uint32 current_time,ellapsed_time;
    Uint32 start_time;
    for (;;)
    {
        start_time = SDL_GetTicks();
        while (SDL_PollEvent(&event))
        {
            switch(event.type)
            {
                case SDL_QUIT:
                exit(0);
                break;
            }
        }
        current_time = SDL_GetTicks();
        ellapsed_time = current_time - last_time;
        last_time = current_time;
        angleZ += 0.05 * ellapsed_time;
        angleX += 0.05 * ellapsed_time;
        Dessiner();
        ellapsed_time = SDL_GetTicks() - start_time;
        if (ellapsed_time < 10)
        {
            SDL_Delay(10 - ellapsed_time);
        }
    }
    return 0;
}
void Dessiner()
{
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity( );
    gluLookAt(3,4,2,0,0,0,0,0,1);
    glRotated(angleZ,0,0,1);
    glRotated(angleX,1,0,0);
    glBegin(GL_QUADS);
    glColor3ub(255,0,0); //face rouge
    glVertex3d(1,1,1);
    glVertex3d(1,1,-1);
    glVertex3d(-1,1,-1);
    glVertex3d(-1,1,1);
    glColor3ub(0,255,0); //face verte
    glVertex3d(1,-1,1);
    glVertex3d(1,-1,-1);
    glVertex3d(1,1,-1);
    glVertex3d(1,1,1);
    glColor3ub(0,0,255); //face bleue
    glVertex3d(-1,-1,1);
    glVertex3d(-1,-1,-1);
    glVertex3d(1,-1,-1);
    glVertex3d(1,-1,1);
    glColor3ub(255,255,0); //face jaune
    glVertex3d(-1,1,1);
    glVertex3d(-1,1,-1);
    glVertex3d(-1,-1,-1);
    glVertex3d(-1,-1,1);
    glColor3ub(0,255,255); //face cyan
    glVertex3d(1,1,-1);
    glVertex3d(1,-1,-1);
    glVertex3d(-1,-1,-1);
    glVertex3d(-1,1,-1);
    glColor3ub(255,0,255); //face magenta
    glVertex3d(1,-1,1);
    glVertex3d(1,1,1);
    glVertex3d(-1,1,1);
    glVertex3d(-1,-1,1);
    glEnd();
    glFlush();
    SDL_GL_SwapBuffers();
}

En comparaison, pour une animation de la même fluidité, la charge moyenne du processeur est négligeable :

Notes diverses :

  • il est aussi possible d'utiliser des timers pour limiter les FPS (voir la doc de SDL_AddTimer ainsi que son exemple). Ce n'est pas la solution retenue ici ;

  • les adeptes de la doc ne manqueront pas de signaler le problème de la granularité du temps de pause (cf. SDL_Delay). Dans les tests effectués pour rédiger ce tutoriel, le temps de pause réel n'excédait jamais le temps demandé de plus de 1 milliseconde. Le nombre réel d'images par seconde est donc égal (ou très proche) au nombre fixé ;

  • en terme d'ergonomie cela peut faire peur à certains de faire s'endormir le programme un certain temps. Qu'ils soient rassurés, même dans une application 3D bien plus complexe, la gestion des événements n'est en rien altérée.

Téléchargez le projet Code::Blocks, l'exécutable Windows et le Makefile Unix (117 Ko)

Téléchargez la vidéo au format avi/Xvid (414 Ko)

Voilà le mystère de la 3D en OpenGL est enfin tombé !
Vous savez maintenant créer de toutes pièces un objet 3D et réaliser une animation.
En attendant le prochain chapitre sur les textures vous pouvez, si vous le souhaitez, améliorer votre programme pour créer de multiples objets, en animer certains grâce au clavier , et par exemple faire tourner la caméra autour de votre scène .
Bonne création !

Le Z-Buffer Les textures

Les textures

Animation Charger une texture

Maintenant que nous savons faire des objets en 3D, en couleur certes mais un peu moches il faut l'avouer :lol: , il est temps de les habiller grâce aux textures. Nous verrons donc dans ce chapitre les rudiments du texturing , comment charger une texture et l'appliquer sur un objet. De quoi nous ouvrir la voie vers des scènes 3D de plus en plus réalistes.

Charger une texture

Les textures Plaquage de texture

Charger une texture

Grâce à notre choix d'utiliser OpenGL avec la SDL, vous allez voir que la phase de chargement des textures nous sera hautement simplifiée.

Mais avant même de pouvoir charger une quelconque texture, encore faut-il en avoir... Pour cela je vous propose de travailler avec ce pack de textures hautes résolutions. Il est assez volumineux (124 Mo) mais assez complet et contient des textures photoréalistes classées dans diverses catégories : sols, caisses, végétaux, métaux, pierres, etc.

Exemples de textures incluses dans le pack

Que vous le téléchargiez ou non je fournirai quand même les textures utilisées dans l'exemple en fin de chapitre. Vous pouvez donc vous en contenter.

Format et taille

Nous utiliserons principalement deux formats pour les textures : .jpg et .png.
Le format .jpg est parfait pour les textures, car il donne les plus petites tailles de fichier sur des images complexes (comme les textures photoréalistes).
Le format .png (24 bits) quant à lui est utilisé, car il gère très bien la transparence.

La largeur et la hauteur des textures doivent impérativement être des puissances de 2 (64,128,512,1024). En effet si elles ne le sont pas, les textures seront de toute façon redimensionnées en interne pour respecter cette contrainte et vous risquez donc de perdre inutilement en qualité.

En terme de qualité visuelle, plus la résolution de la texture est grande, meilleur est le résultat. Le pack que je vous fournis contient principalement des textures haute-résolution (512x512) avec lesquelles vous n'aurez pas de mal à avoir des rendus de meilleure qualité que Half-Life premier du nom :soleil: (effets de lumière mis à part pour l'instant).

Utiliser SDL_Image pour charger une texture

Le code nécessaire pour créer une texture OpenGL à partir d'un tableau de pixels n'est pas extrêmement compliqué. Cependant la phase la plus pénible est le chargement d'un fichier image (.jpg, .bmp, .tga ou autre).
Heureusement pour nous SDL_Image est là ; nous aurons simplement à l'utiliser pour qu'elle nous retourne une SDL_Surface à partir d'un nom de fichier.

Une fois cette surface créée, elle doit être retournée verticalement car SDL et OpenGL n'ont pas les mêmes conventions. Vous avez dû le remarquer lors du chapitre sur les transformations car le (0,0) en OpenGL était en bas à gauche alors que celui en SDL (comme indiqué dans le cours de M@teo) est en haut à gauche.

La dernière chose à faire est de convertir le tableau de pixels contenus dans la surface en texture OpenGL par des appels OpenGL appropriés.

Oh là là ! Ça a l'air compliqué tout ça... Image utilisateur :( Je vais réussir à faire ça moi ?

Savoir coder vous-mêmes cette phase n'est pas nécessaire pour la compréhension de la suite du chapitre. Le plus important est l'utilisation des textures créées. Nous reviendrons plus en détail sur les appels en question lorsque nous verrons les textures procédurales mais pour l'instant je vous donne tout ça sur un plateau d'argent, voilà :

Téléchargez le code pour charger une texture (3 Ko)

Dans l'archive, je vous fournis deux fichiers : sdlglutils.h et sdlglutils.cpp. J'ai choisi ce nom car j'y rajouterai petit à petit des fonctions utiles pour ce tuto qui ne sont là que pour nous simplifier la vie mais que vous auriez pu faire vous-mêmes avec un peu plus de connaissances.

La fonction qui nous intéresse pour l'instant est loadTexture qui s'utilise très simplement :

#include "sdlglutils.h"
GLuint identifiant_texture = loadTexture("ma_texture.jpg");

Si vous êtes curieux vous pouvez regarder le code derrière mais il n'a rien de compliqué, juste ce que je vous ai expliqué plus haut (chargement de l'image, retournement, et création texture OpenGL).

Le type renvoyé est GLuint soit l'équivalent d'un unsigned int. Cependant je n'utiliserai jamais le type unsigned int pour les textures pour ne pas être tenté dans mon code de faire des calculs dessus, en effet ce nombre retourné a une signification bien précise : c'est l'identifiant de la texture OpenGL créée. À chaque fois que nous voudrons utiliser cette texture, il suffira d'utiliser cet identifiant.

Activer le texturing

Comme nous avons déjà créé des scènes en 3D sans texture nous savons que le texturing n'est pas activé par défaut. Pour l'activer il suffit donc d'appeler :

glEnable(GL_TEXTURE_2D);

Vous pouvez avoir envie dans vos scènes de faire cohabiter des objets texturés avec des objets non texturés (pourquoi pas après tout...). Pour ce faire il suffit de désactiver le texturing temporairement avec :

glDisable(GL_TEXTURE_2D);

Voilà nous sommes prêts, nous savons charger une texture, nous savons activer le texturing. Maintenant voyons comment, en reprenant notre cube 3D du chapitre précédent, appliquer une texture sur un objet.

Les textures Plaquage de texture

Plaquage de texture

Charger une texture Texture répétitive

Plaquage de texture

Partons du code que nous avions pour définir la première face de notre cube :

glBegin(GL_QUADS);
glVertex3d(1,1,1);
glVertex3d(1,1,-1);
glVertex3d(-1,1,-1);
glVertex3d(-1,1,1);
glEnd();

Nous allons utiliser la texture « stainedglass05.jpg » que vous trouverez dans le pack dans la catégorie window (ou dans l'archive en fin de chapitre).

Commençons donc par la charger au lancement de notre application comme expliqué précédemment :

GLuint texture1; //en variable globale
int main(int argc, char *argv[])
{
    // ... lancement de l'application
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_TEXTURE_2D);
    texture1 = loadTexture("stainedglass05.jpg"); // pendant l'initialisation d'OpenGL, avant la boucle d'affichage
    //... boucle d'affichage et de gestion des événements
}

Dans le code du dessin, avant de définir les vertices de notre première face texturée, il nous faut dire à OpenGL que l'on veut utiliser cette texture en appelant glBindTexture avec l'identifiant de notre texture :

glBindTexture(GL_TEXTURE_2D, texture1);

Et maintenant nous allons réaliser le plaquage proprement dit de la texture sur la face en faisant correspondre à chaque vertex composant notre face une coordonnée sur la texture comme représenté sur le schéma ci-dessous :

Correspondance coordonnées texture / coordonnées réelles

Pour définir les coordonnées du vertex nous savons utiliser glVertex, eh bien pour définir les coordonnées de texture nous utiliserons :

glTexCoord2d (double x_texture, double y_texture);

L'espace de coordonnées sur la texture est en 2D, le coin en bas à gauche a les coordonnées (0,0) et le coin en haut à droite est en (1,1), quelque soit la taille en pixels de l'image.

Ici je vais commencer par définir le vertex (1,1,1) auquel je veux plaquer le coin haut gauche de la texture soit (0,1).
Je fais donc :

glBegin(GL_QUADS);
    glTexCoord2d(0,1);  glVertex3d(1,1,1);

glTexCoord2d, comme glColor3ub, s'applique à tous les vertices définis par la suite. Il ne faut donc pas oublier d'y faire à nouveau appel avant chaque vertex.

En continuant avec les autres sommets de la face cela donne donc le code complet suivant :

glBindTexture(GL_TEXTURE_2D, texture1);
    glBegin(GL_QUADS);
    glTexCoord2d(0,1);  glVertex3d(1,1,1);
    glTexCoord2d(0,0);  glVertex3d(1,1,-1);
    glTexCoord2d(1,0);  glVertex3d(-1,1,-1);
    glTexCoord2d(1,1);  glVertex3d(-1,1,1);
    glEnd();

Il n'est pas possible de changer de texture en cours de définition d'une face. Pour appliquer une texture différente à la face suivante il faut donc terminer le bloc de vertices par un appel à glEnd(), changer la texture puis définir la prochaine face :

glBindTexture(GL_TEXTURE_2D, texture1);
    glBegin(GL_QUADS); //première face
    //définition des vertices
    glEnd();
    glBindTexture(GL_TEXTURE_2D, texture2); //changement de texture
    glBegin(GL_QUADS); //deuxième face
    //définition des vertices
    glEnd();

Ce n'est bien sûr pas nécessaire si vous souhaitez garder la même texture.

Erreurs fréquentes

Une mauvaise utilisation de glTexCoord2d (mauvaises coordonnées, oublié de redéfinir glTexCoord2d pour le vertex suivant) donne lieu à de drôles de résultats :

Erreur en oubliant de redéfinir glTexCoord2d pour le dernier vertex

:waw: :p Ne rigolez pas ces erreurs sont fréquentes au début et je suis prêt à parier que ça vous arrivera au moins une fois :lol: . Vous saurez au moins d'où vient le problème...

Cas d'un triangle

Dans les jeux vidéos, la primitive de base la plus utilisée est le triangle (nous verrons pourquoi quand nous importerons des models 3D). Le plaquage de texture fonctionne de la même manière en ne choisissant que 3 points sur la texture, points qui ne sont donc pas forcément des coins de l'image.

glBindTexture(GL_TEXTURE_2D, texture2);
    glBegin(GL_TRIANGLES);
    glTexCoord2d(0,0);      glVertex3d(1,1,-1);
    glTexCoord2d(1,0);      glVertex3d(-1,1,-1);
    glTexCoord2d(0.5,1);    glVertex3d(0,0,1);
    glEnd();
Utilisation d'une partie de la texture

Nous venons de le voir dans le cas du triangle, il n'est pas obligatoire d'utiliser toute la texture. C'est une pratique souvent utilisée pour regrouper toutes les textures pour un seul objet dans un seul fichier, comme par exemple ici la skin de Tommy Vercetti dans GTA:Vice City :

Texture du héros (une des skins possibles) de GTA:Vice City

Il suffit alors, à coup de glTexCoord2d bien pensés, de délimiter la zone de la texture que l'on souhaite utiliser pour la face courante :

glBindTexture(GL_TEXTURE_2D, texture3);
    glBegin(GL_QUADS);
    glTexCoord2d(0,1);  glVertex3d(1,1,1);
    glTexCoord2d(0,0);  glVertex3d(1,1,-1);
    glTexCoord2d(0.33,0);  glVertex3d(-1,1,-1);
    glTexCoord2d(0.33,1);  glVertex3d(-1,1,1);
    glEnd();

Comme vous le voyez, la texture utilisée ici n'est pas un carré (384x128) et pourtant le coin en haut à droite a toujours les coordonnées (1,1). Les coordonnées d'un point sur la texture donnent donc une information relative à la taille totale de l'image. Ici on a voulu découper une partie de 128 pixels de large sur les 384 totaux soit un rapport de 1/3 (0.33).

Charger une texture Texture répétitive

Texture répétitive

Plaquage de texture Les couleurs

Texture répétitive

Jusqu'à présent nous appliquions tout ou une partie de la texture sur nos objets. Mais si on essaye de créer un sol (un simple carré de 20x20) dans la scène avec la même technique on obtient le résultat suivant :

glBindTexture(GL_TEXTURE_2D, texture4);
    glBegin(GL_QUADS);
    glTexCoord2i(0,0);      glVertex3i(-10,-10,-1);
    glTexCoord2i(1,0);     glVertex3i(10,-10,-1);
    glTexCoord2i(1,1);    glVertex3i(10,10,-1);
    glTexCoord2i(0,1);     glVertex3i(-10,10,-1);
    glEnd();

Comme vous le voyez la texture n'est pas faite pour être étalée sur une si grande surface. Nous devons donc faire en sorte qu'elle se répète !
Dans les exemples que nous avons vus nous n'utilisions que des coordonnées entre 0 et 1. Mais en réalité l'espace de coordonnées de la texture n'a pas de limite :

Espace des coordonnées de texture

Quand nous considérions l'image d'origine, nous nous restreignions à une partie de cet espace. Mais ici nous voulons faire répéter la texture 10 fois par exemple donc nous allons prendre des coordonnées entre 0 et 10 tout simplement !

Et en effet en modifiant le code en conséquence :

glBindTexture(GL_TEXTURE_2D, texture4);
    glBegin(GL_QUADS);
    glTexCoord2i(0,0);      glVertex3i(-10,-10,-1);
    glTexCoord2i(10,0);     glVertex3i(10,-10,-1);
    glTexCoord2i(10,10);    glVertex3i(10,10,-1);
    glTexCoord2i(0,10);     glVertex3i(-10,10,-1);
    glEnd();

Nous obtenons un bien meilleur résultat visuel :

Texture du sol répétée 10 fois

Toutes les textures ne sont pas faites pour être répétées. Il faut en effet qu'elles soient conçues spécialement pour les bords correspondent quand plusieurs répétitions de l'image sont mises bout à bout. Dans le pack de textures, vous pouvez être quasiment sûrs que toutes les textures de sol, de mur, de plafond, d'herbe, de rocher sont susceptibles d'être répétées.

Plaquage de texture Les couleurs

Les couleurs

Texture répétitive Les quadriques

Les couleurs

Je n'ai volontairement pas mentionné les couleurs depuis le début de ce chapitre pour rester concentré sur la nouveauté du moment : les textures. Mais nos bonnes vielles couleurs ne sont pas mortes pour autant.

Combiner texture et couleur

Nous savons depuis le début de ce tuto définir la couleur des vertices à venir avec glColor3ub. Rien ne nous interdit de continuer à l'utiliser en plus de ce que nous venons d'apprendre sur les textures. La définition complète d'un vertex peut donc maintenant contenir jusqu'à 3 lignes :

  • définition de la couleur avec glColor3ub (facultatif) ;

  • définition des coordonnées sur la texture avec glTexCoord2d (obligatoire sur utilisation d'une texture) ;

  • définition des coordonnées spatiales du vertex avec glVertex3d.

Il n'est bien sûr pas obligatoire de redéfinir glColor3ub à chaque fois si l'on ne souhaite pas changer de couleur.
Appliqué au sol vu précédemment, en affectant des couleurs à chaque sommet on obtient donc :

glBindTexture(GL_TEXTURE_2D, texture4);
    glBegin(GL_QUADS);
    glColor3ub(255,0,0); //Nouveau
    glTexCoord2i(0,0);
    glVertex3i(-10,-10,-1);
    glColor3ub(0,255,0); //Nouveau
    glTexCoord2i(10,0);
    glVertex3i(10,-10,-1);
    glColor3ub(255,255,0); //Nouveau
    glTexCoord2i(10,10);
    glVertex3i(10,10,-1);
    glColor3ub(255,0,255); //Nouveau
    glTexCoord2i(0,10);
    glVertex3i(-10,10,-1);
    glEnd();

Comme vous le voyez, la couleur agit comme un filtre et vient donner une teinte locale à la texture.
Tout semble aller très bien jusqu'à ce que l'on décide d'afficher autre chose juste après avoir défini notre sol, un cube texturé par exemple :

Comme vous le voyez le cube est lui aussi affecté par la dernière couleur utilisée. C'est tout à fait logique car glColor3ub par définition est utilisé pour tous les vertex définis à la suite (jusqu'au prochain appel à glColor3ub).

Il faut donc utiliser une couleur neutre qui, appliquée comme filtre, ne viendra pas modifier la couleur de la texture. Et cette couleur n'est autre que... le blanc !

Ainsi, pour en quelque sorte « annuler l'effet des couleurs », il suffit d'utiliser le blanc comme prochaine couleur avec un appel à :

glColor3ub(255,255,255);

Et en effet en interposant un appel à glColor3ub(255,255,255); entre la définition du sol et celle du cube, on obtient bien un cube vierge de tout effet de couleur :

//... début du sol
    glColor3ub(255,0,255);
    glTexCoord2i(0,10);
    glVertex3i(-10,10,-1);
    glEnd(); //fin du sol
    glColor3ub(255,255,255); //on enlève la couleur
    glBegin(GL_QUADS); //début du cube
    glTexCoord2d(0,1);
    glVertex3d(1,1,1);
    //... fin du cube

Voilà vous savez maintenant les précautions qu'il faut prendre lorsque l'on souhaite combiner dans un même code les couleurs et les textures. Rassurez-vous, il ne vous sera pas rare d'oublier de repasser en blanc de temps en temps et vous créerez souvent de jolis effets de couleur involontairement. ;)

Qui aurait cru que nous passerions aussi rapidement d'objets moches mais joliment colorés à de somptueux objets texturés ?!
Comme vous avez pu le voir il n'y a vraiment rien de sorcier dans l'application des textures, il suffit de bien savoir affecter à chaque vertex les bonnes coordonnées sur la texture et le tour est joué.
Le meilleur moyen de vous assurer que vous avez bien compris est de vous entraîner à réaliser une petite scène en 3D avec des textures, notamment une caisse de 4x2x2 avec la texture de caisse (voir caisse.jpg dans zip plus bas) utilisée dans ce chapitre.

Je vous en ai fait une rapidement dont vous pouvez télécharger le code plus bas :

Laissez libre cours à votre imagination et n'hésitez pas à poster vos créations dans les commentaires du chapitre ou sur le forum.

Téléchargez les textures utilisées et un exemple de scène texturée (482 Ko)

Téléchargez la vidéo au format avi/Xvid (1.10 Mo)

Dans le prochain chapitre nous verrons comment créer des formes un peu plus complexes qu'un simple cube ou une pyramide, toujours dans le but d'enrichir le contenu de nos scènes.

Texture répétitive Les quadriques

Les quadriques

Les couleurs Principe d'utilisation

Le mot quadrique, équivalent de « surface quadratique », a une connotation mathématique qui peut faire peur au premier abord. :euh:
Mais ce n'est rien d'autre qu'une surface non-linéaire (pas plane), et derrière ce nouveau mot encore plus barbare, OpenGL regroupe en fait la sphère, le cône, le cylindre, et le disque.
Nous verrons donc dans ce court chapitre comment enrichir un peu plus vos scènes avec ces quelques formes prédéfinies qui nous éviteront bien des efforts.

Principe d'utilisation

Les quadriques Les quadriques

Principe d'utilisation

Nous le verrons juste après, il existe dans OpenGL des fonctions toutes faites pour dessiner une sphère, un cylindre, etc.
Cependant ces fonctions ont besoin d'informations sur les intentions du codeur : faut-il texturer l'objet à créer, faut-il l'afficher uniquement avec des traits ?
Pour cela nous utiliserons un champ ( struct) de paramètres. Ces paramètres sont stockés dans une variable de type GLUquadric que l'on se doit d'utiliser d'une manière un peu particulière.

Création d'une variable de type GLUquadric

Ce n'est pas à nous directement de créer une variable de ce type. On doit utiliser un appel OpenGL qui nous renverra un pointeur sur le GLUquadric créé par OpenGL :

GLUquadric* params;
params = gluNewQuadric();

Nous pouvons donc maintenant utiliser la variable params créée pour paramétrer nos objets et les dessiner.

Paramétrage du GLUquadric

Ce champ n'est pas manipulable directement mais seulement par l'intermédiaire de fonctions. Une fois paramétré il sera utilisé pour tous les appels de dessin de quadriques définis après.

Style d'affichage

Les fonctions pour définir des quadriques utilisent tout comme nous des appels à glVertex pour définir les vertices des objets. Cependant comme nous n'avons pas accès directement au code, et donc au glBegin, nous ne pouvons pas spécifier par exemple par un glBegin(GL_LINES); que nous voulons que l'affichage soit fait avec des lignes.

Pour ce faire nous devons utiliser la fonction :

gluQuadricDrawStyle(params,style);
qui permet de définir quel sera le style d'affichage. style peut valoir :

Style | Explication | Exemple
GLU_POINT | L'objet sera dessiné avec des points. |
GLU_LINE | L'objet sera dessiné avec des lignes. |
GLU_FILL | L'objet sera dessiné avec des faces pleines. |

La valeur par défaut est GLU_FILL. Pour utiliser des faces pleines il n'est donc pas nécessaire d'appeler gluQuadricDrawStyle si aucun style n'a été précédemment défini.

Coordonnées de texture

Nous l'avons vu dans le précédent chapitre, pour utiliser des textures il faut définir des coordonnées de texture avec glTexCoord2d. Si nous souhaitons utiliser des textures avec nos quadriques, il faut indiquer à OpenGL qu'il doit lui aussi incorporer les appels à glTexCoord2d lorsqu'on demandera de dessiner un quadrique. Nous utilisons donc la fonction :

gluQuadricTexture(params,texture);

où texture peut valoir GL_TRUE (vrai : pour activer les coordonnées de texture) ou GL_FALSE (faux : pour ne pas utiliser les coordonnées de texture).

La valeur par défaut étant GL_FALSE, il n'est nécessaire d'appeler initialement gluQuadricTexture que si nous souhaitons utiliser les textures sur nos quadriques.

glBindTexture(GL_TEXTURE_2D,texture1);
    GLUquadric* params = gluNewQuadric();
    //dessin de la sphere... (à venir)
    gluDeleteQuadric(params);

glBindTexture(GL_TEXTURE_2D,texture1);
    GLUquadric* params = gluNewQuadric();
    gluQuadricTexture(params,GL_TRUE);
    //dessin de la sphere... (à venir)
    gluDeleteQuadric(params);

Sur la première image c'était pas censé être une sphère ? On dirait un simple disque...

Sans texture et sans lumière, on ne peut pas comprendre que c'est une sphère. C'est un principe d'optique : la compréhension de la forme d'un objet sur une image 2D utilise pour beaucoup les différences de couleurs dues à l'éclairage ( Shape from Shading). Nous réglerons donc ce problème dans quelques chapitres lorsque nous verrons la lumière . En attendant, avec les textures et le mouvement nous n'aurons vraiment pas de mal à discerner la forme de nos objets rassurez-vous.

Suppression du GLUquadric

Même si nous n'avons pas nous-mêmes utilisé de malloc (ou new en C++) pour créer le GLUquadric, il faut libérer la mémoire quand on ne souhaite plus l'utiliser par le biais de la fonction (entraperçue dans les exemples de code plus haut) :

gluDeleteQuadric(params);

Maintenant nous savons créer un champ de paramètres pour nos quadriques, le paramétrer et le supprimer. Il est temps de voir ce qui nous intéresse réellement, les quadriques et leurs fonctions de dessin !

Les quadriques Les quadriques

Les quadriques

Principe d'utilisation Exercice : une roquette

Les quadriques

Pour bien comprendre quelles sont les faces et vertices générés par les appels suivants, tous les quadriques seront représentés en filaire. J'inclus aussi une version 3D du repère pour montrer que l'axe Z (bleu) est l'axe principal utilisé par les quadriques.

La sphère

gluSphere(GLUquadric* params,radius,slices,stacks);

  • Le premier paramètre est le champ de type GLUquadric que nous avons paramétré précédemment.

  • Le deuxième, radius, est le plus simple : c'est le rayon de la sphère.

  • slices est le nombre de tranches verticales qui composeront la sphère.

  • stacks est aussi un nombre de tranches mais pour les tranches horizontales.

L'influence des ces deux paramètres est résumée par l'image ci-dessous :

Plus ces deux nombres sont grands, plus la sphère est précise et ressemble en effet à une sphère. La valeur choisie (20x20) donne un résultat satisfaisant.

Le cylindre et le cône

gluCylinder(GLUquadric* params,base,top,height,slices,stacks);

  • Le premier paramètre est toujours le même champ de type GLUquadric.

  • Base est le rayon du cylindre en bas, top est le rayon du cylindre en haut. Pour avoir un vrai cylindre il faut donc utiliser la même valeur pour les 2, mais en utilisant des valeurs différentes pour base et top, nous aurons un cône ! (voir dessin ci-dessous)

  • slices est, comme pour la sphère, le nombre de divisions autour de l'axe Z et nous choisirons une valeur de l'ordre de 20 pour la même raison.

  • stacks ici ne sert pas à grand chose. Mettre une valeur différente de 1 ne changerait rien au niveau de la précision du cylindre/cône (à part quand on le regarde en filaire).

gluCylinder(params,1,0,2,20,1);

Le disque

gluDisk(GLUquadric* params,inner,outer,slices,loops);

  • Le premier paramètre est toujours le même champ de type GLUquadric.

  • inner est le rayon interne du disque, souvent à 0 mais peut (comme sur l'image) avoir une valeur différente.

  • outer est le rayon externe du disque.

  • slices est, comme précédemment, le nombre de divisions autour de l'axe Z.

  • loops ici ne sert pas à grand chose. Mettre une valeur différente de 1 rajouterait des faces à l'intérieur du disque mais ne rajoute pas de précision visible (à part en filaire).

Le disque partiel

gluPartialDisk(GLUquadric* params,inner,outer,slices,loops,start,sweep);

Le disque partiel est comme le disque normal sauf qu'il ne fait pas nécessairement 360°.

  • start est l'angle de départ du disque partiel. Malheureusement pour nous, les concepteurs d'OpenGL n'ont pas suivi la logique mathématique qui voudrait que les angles soient exprimés dans le sens trigonométrique (sens inverse des aiguilles d'une montre) avec 0° sur l'axe X. Ici 0° pour start place le début du disque sur l'axe Y (vert), 90° sur l'axe X (rouge).

  • sweep est la distance angulaire entre le début et la fin du disque partiel (180° sur le dessin plus haut).

La précision donnée par slices s'applique ici au disque partiel uniquement. Cela ne sert donc à rien de mettre une grande valeur (20) si on utilise un disque partiel de 90° seulement. Une précision de 5 peut suffire à avoir le même résultat de qualité sur un angle de 90° (par rapport à une précision de 20 pour un angle de 360°).

Un même GLUquadric pour dessiner plusieurs quadriques

Vous l'avez compris maintenant, l'objet GLUquadric n'est pas un quadrique mais simplement un champ de paramètres utilisé lors de l'appel d'une fonction de dessin de quadrique pour spécifier le mode d'affichage. On peut donc tout à fait utiliser le même GLUquadric pour faire dessiner des quadriques tout en changeant, si on le souhaite, les paramètres en cours de route :

glBindTexture(GL_TEXTURE_2D,texture1);
    GLUquadric* params = gluNewQuadric();
    gluQuadricDrawStyle(params,GLU_LINE);
    gluCylinder(params,1,1,2,20,1);
    gluQuadricDrawStyle(params,GLU_FILL);
    gluQuadricTexture(params,GL_TRUE);
    glTranslated(0,0,1);
    gluSphere(params,0.75,20,20);
    gluDeleteQuadric(params);
e d'utilisation Exercice : une roquette

Exercice : une roquette

Les quadriques Contrôle avancé de la caméra (Partie 1/2)

Exercice : une roquette

Ce chapitre n'a rien de compliqué mais jusqu'à présent j'ai fait tout le boulot en vous détaillant les fonctions pour utiliser les quadriques. Maintenant à vous ! :diable:
Pour vous faire la main sur ces nouvelles fonctions je vous propose de créer une roquette, basée sur celles que l'on trouve dans le jeu Half-Life premier du nom. Mon but n'est pas de faire de vous des apprentis terroristes mais juste des pros des quadriques ! :ange:

Une roquette inspirée d'Half-Life

Les textures

Les textures sont elles aussi tirées d'Half-Life et légèrement modifiées par mes soins. Vous les trouverez dans le pack ci-dessous (et dans le zip final).

Téléchargez les textures pour la roquette (4.53 Ko)

Schéma de la roquette

Comme vous pouvez le voir, la roquette est constituée de quatre éléments :

  1. un cône supérieur (texture rocket_top.jpg) ;

  2. un cône intermédiaire (texture rocket_middle.jpg) ;

  3. un cône inférieur (texture rocket_bottom.jpg) ;

  4. et enfin, ça ne se voit pas trop sur le schéma, un disque (texture rocket_motor.jpg).

Méthode pour la coder

Pour coder la roquette il nous faut donc procéder en deux étapes :

  • au lancement du programme il faut : charger toutes les textures utilisées ;

  • au moment de dessiner il faut :

    1. créer un GLUquadric ;

    2. paramétrer le quadrique pour qu'il génère les coordonnées de texture automatiquement ;

    3. dessiner le premier objet ;

    4. se translater à la base du deuxième objet ;

    5. dessiner le deuxième objet ;

    6. etc.

    7. détruire le GLUquadric.

La phase se translater est importante. Comme nous l'avons vu sur les schémas des différents types de quadriques, ils sont toujours dessinés à partir de (0,0,0) dans le repère local. En utilisant intelligemment les transformations, il est donc possible de placer chaque quadrique où on le souhaite.

N'oubliez pas non plus de changer de texture entre chaque quadrique pour ne pas vous retrouver avec une roquette uniformément... moche. ;)

Voilà vous avez tous les outils pour dessiner cette roquette. Référez-vous bien au schéma que je vous donne pour respecter les proportions. Toute roquette déformée ne sera pas acceptée pour une utilisation sur le terrain ! (Hum...)

À vous donc !

Correction

Je ne vous mets ici que le code intéressant. Je pars du principe que vous savez parfaitement charger les textures et initialiser l'application. Je fournis bien entendu le code complet dans l'archive finale.

/* J'ai choisi de faire une fonction Dessiner Rocket.
Je pourrai ainsi l'appeler plusieurs fois, et dans n'importe quelle
position initiale du repère initial */
void DrawRocket()
{
    glPushMatrix(); //pour que les transformations soient réversibles
    GLUquadric* params = gluNewQuadric(); //création du quadrique
    gluQuadricTexture(params,GL_TRUE); //activation des coordonnées de texture
    glBindTexture(GL_TEXTURE_2D,top); //texture du haut
    gluCylinder(params,0.5,0,1.6,20,1); //cône 1
    glBindTexture(GL_TEXTURE_2D,middle);
    glTranslated(0,0,-1.05); //je descends pour faire le 2ème cône
    gluCylinder(params,0.15,0.5,1.05,20,1); //cône 2
    glBindTexture(GL_TEXTURE_2D,bottom);
    glTranslated(0,0,-0.25); //je descends enfin tout en bas (sur le schéma)
    gluCylinder(params,0.3,0.15,0.25,20,1); //cône 3
    //et à la même position je dessine le disque de sortie des flammes
    glBindTexture(GL_TEXTURE_2D,motor);
    gluDisk(params,0,0.3,20,1); //disque 4
    gluDeleteQuadric(params); //je supprime le quadrique
    glPopMatrix(); //hop je remets tout comme je l'ai trouvé
}
void DrawGL()
{
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity( );
    gluLookAt(3,4,2,0,0,0,0,0,1); //je place la caméra à un endroit idéal
    DrawRocket(); //je dessine la 1ère roquette
    glTranslated(2,0,0); //je me déplace pour la 2ème roquette
    glRotated(90,1,0,0); /*je vais tourner celle-là pour que son axe principal
    soit horizontal */
    DrawRocket(); //et je la dessine
    glFlush();
    SDL_GL_SwapBuffers();
}

Comme vous le voyez j'ai décidé personnellement de faire une fonction pour dessiner la roquette. Je peux ainsi dessiner autant de roquettes que je veux sans alourdir le code.

Améliorations

Vous pouvez, si vous le souhaitez, créer une sphère représentant la Terre (avec la texture EarthLow.jpg du pack final) autour de laquelle la roquette tournerait.
Conseil : pour faire tourner la roquette autour de la Terre il suffit de bien réfléchir à l'ordre des transformations à faire. Comme ce n'est pas le but de cet exercice, qui se veut simple et rapide, je vous laisse imaginer la solution adéquate.

Téléchargez le projet Code::Blocks, l'exécutable Windows et le Makefile Unix (391 Ko)

Les quadriques, c'est fantastique et c'est magique ! Loin d'être mystiques elles sont quand même vachement pratiques ! :soleil:
C'est une solution très simple pour ajouter des objets un peu complexes dans vos scènes en attendant de savoir charger de vrais modèles 3D créés dans des logiciels 3D externes (Blender, 3dSmax...).
Dans le prochain chapitre nous verrons comment contrôler la caméra de manière plus poussée, et réaliserons entre autres un dérivé de Google Earth sans prétention qui utilisera justement la sphère. Que de bonheur en perspective !

Les quadriques Contrôle avancé de la caméra (Partie 1/2)

Contrôle avancé de la caméra (Partie 1/2)

Exercice : une roquette Principe d'une caméra TrackBall

Ce chapitre en deux parties vient présenter comment créer des caméras contrôlables dans vos applications OpenGL. Fini donc le calvaire de prévoir précisément dans le code la position/orientation de la caméra.
Nous commencerons dans ce chapitre par une caméra Trackball, assez simple à implémenter et qui nous permettra d'introduire le concept de classes en C++.

Principe d'une caméra TrackBall

Contrôle avancé de la caméra (Partie 1/2) Quelques bases de C++

Principe d'une caméra TrackBall

Le nom TrackBall vient de ce périphérique bizarre qui remplace la souris où l'on tourne directement une boule. Ici j'ai librement tiré le terme du logiciel multiplate-forme Google Earth :

Google Earth

Dans Google Earth en effet on utilise la souris pour tourner autour de la Terre. Nous allons donc reproduire ce principe qui nous permettra d'avoir une caméra permettant de regarder un objet / une partie d'une scène sous tous les angles.

Rotation à la souris

En maintenant le bouton gauche de la souris enfoncé, les mouvements de la souris feront tourner la scène :

  • un mouvement horizontal de la souris donne une rotation horizontale de la scène (donc autour de sa verticale).

  • un mouvement vertical de la souris donne une rotation verticale de la scène.

Ces mouvements sont illustrés par les schémas ci-dessous :

Mouvement horizontal de la souris

Mouvement vertical de la souris

Notez au passage, et nous verrons l'équivalent dans le code, que ce n'est pas vraiment la caméra qui tourne mais la scène, même si cela revient plus ou moins au même. Ce n'est le cas que pour ce type de caméra. Pour la caméra FreeFly que nous verrons après c'est bel et bien la caméra et non la scène qui bougera.

Zoom à la molette

Pour prendre du recul ou au contraire nous rapprocher de l'objet / scène que nous souhaitons visualiser, nous allons tout simplement utiliser la molette. Un coup de molette en avant pour zoomer, un coup de molette en arrière pour dézoomer, rien de plus intuitif :

Rotation de la roulette de la souris

Ici c'est bien la caméra qui bouge, nous verrons une fois encore comment cela se répercute sur le code.

Et le clavier ?

Il est possible d'arguer que toutes les souris ne possèdent pas de molette. Dans ce cas-là rien ne vous empêche d'utiliser le clavier pour dézoomer.
Ici nous n'utiliserons le clavier que pour une chose : réintialiser la rotation de la scène avec la touche

(SDLK_HOME avec SDL).

Contrôle avancé de la caméra (Partie 1/2) Quelques bases de C++

Quelques bases de C++

Principe d'une caméra TrackBall Implémentation de la caméra

Quelques bases de C++

Maintenant que nous savons ce que nous voulons faire avec notre caméra, il faut faire un petit intermède apprentissage du C++.
Nous allons en effet utiliser et regrouper toutes les fonctionnalités de notre caméra dans une classe : TrackBallCamera.

Le cours de M@teo expliquera le concept des classes en détail. Voyons pour l'instant ça comme une extension d'une structure.

Rappelez-vous en C un struct permettait de stocker plusieurs champs dans un même type :

struct NomDeVotreStructure
{
    long variable1;
    long variable2;
    int autreVariable;
    double nombreDecimal;
};

Une classe possède, en plus des attributs, des méthodes. Ces méthodes sont comme des fonctions qui s'appliquent aux instances de cette classe.

Ouh là là beaucoup de mots nouveaux ! Instances par exemple c'est quoi ?

Imaginons une classe nommée Chaise. Une chaise a certains attributs : hauteur, nombre de pieds, matière.
On écrira donc :

class Chaise
{
protected:
        int hauteur;
        int nombre_de_pieds;
        string matiere;
}

Une fois la classe déclarée, dans le c%u0153ur du programme on veut pouvoir en utiliser (des chaises). On crée donc des « instances » de la classe « Chaise » en déclarant simplement une variable de type Chaise.
Exemple :

Chaise machaise;

Notez qu'en C++ les structures et les classes sont automatiquement des types. Nul besoin donc d'écrire un typedef Class Chaise Chaise; par exemple.

Vous avez pu voir un mot bizarre dans mon exemple : protected. Sans rentrer dans le détail, cela veut dire que les attributs déclarés protected ne sont pas accessibles de l'extérieur de la classe mais uniquement par ses méthodes.

Les méthodes justement c'est quoi ?

Une méthode est comme une fonction mais elle s'applique à une instance précise de la classe.
Reprenons notre exemple de la chaise. Imaginons que nous voulions enlever un pied à notre chaise.
En C nous aurions dû utiliser une fonction enleverPied en passant en paramètre quelle chaise modifier.

En C++ on appelle directement une méthode sur une instance de la classe.
Exemple :

La déclaration de la classe Chaise dans Chaise.h

class Chaise
{
public:
        void enleverPied();
protected:
        int hauteur;
        int nombre_de_pieds;
        string matiere;
};

L'implémentation des méthodes de la classe Chaise dans Chaise.cpp

#include "chaise.h"
void Chaise::enleverPied()
{
        nombre_de_pieds--;
}

Appel dans le corps du programme
Et maintenant ce qui nous intéresse, l'appel de la méthode enleverPied sur une instance :

Chaise machaise;
machaise.enleverPied();

Comme vous le voyez on appelle une méthode comme on utiliserait un attribut : instance.laméthode();
Quand le programme entre dans le code de la méthode il l'applique donc à l'instance souhaitée, et utilise donc les attributs propres à l'instance en question.

Notez qu'ici je mets la méthode en public et non protected pour pouvoir l'appeler de l'extérieur de la classe (c'est-à-dire à partir du corps du programme).

Deux méthodes particulières

Il existe deux méthodes particulières qui ne sont pas appelées directement par l'utilisateur : le constructeur et le destructeur.

Le constructeur est appelé lorsque l'objet est initialisé, généralement à sa déclaration.
C'est une méthode sans type de retour, qui porte le nom de la classe, et qui permet d'initialiser les attributs à des valeurs initiales :

Exemple :

Déclaration du constructeur dans Chaise.h

Class Chaise
{
public:
        Chaise(); //un constructeur ne renvoit rien mais peut éventuellement avoir des paramètres
        void enleverPied();
protected:
        int hauteur;
        int nombre_de_pieds;
        string matiere;
};

Implémentation du constructeur dans Chaise.cpp

Chaise::Chaise()
{
        hauteur = 1;
        nombre_de_pieds = 4;
        matiere = "bois";
}

Et donc ce constructeur sera appelé dès qu'on instanciera un objet dans le corps principal du programme :

Chaise machaise; //déclenche l'appel du constructeur
machaise.enleverPied(); //je sais donc que maintenant elle en a 3 car une chaise a 4 pieds au départ grâce au contructeur

Le destructeur quant à lui est appelé automatiquement quand on détruit l'objet. Dans le cas présent je n'ai rien de spécial à faire dans le destructeur, mais si nous avions alloué de la mémoire dynamiquement (attributs dynamiques de la classe), c'est dans le destructeur qu'il faut les détruire pour ne pas faire de fuite de mémoire. Comme le constructeur, le destructeur porte le nom de la classe précédé du symbole « ~ ». Ici je vais me contenter d'afficher un message lors de la destruction :

Déclaration du destructeur dans Chaise.h

Class Chaise
{
public:
        Chaise(); //un constructeur ne renvoie rien mais peut éventuellement avoir des arguments
        void enleverPied();
        ~Chaise(); //un destructeur ne renvoie rien, n'a pas d'arguments, et se précède du symbole ~
protected:
        int hauteur;
        int nombre_de_pieds;
        string matiere;
};

Implémentation du destructeur dans Chaise.cpp

#include <iostream>
...
Chaise::~Chaise()
{
        std::cout << "Au revoir petite chaise." << std::endl;
}

Dans le corps du programme l'appel au destructeur est automatique à la fin du bloc où l'instance est déclarée.
Exemple :

int main()
{
        Chaise machaise; //appel du constructeur
        machaise.enleverPied(); //la pauvre ça doit faire mal
        return 0; //on quitte le bloc du main, donc on détruit toutes les variables -> appel automatique du destructeur de Chaise sur l'instance machaise.
}
Allocation dynamique

Si vous en êtes à la lecture du tuto OpenGL c'est que vous connaissez sûrement l'allocation dynamique en C :

struct Chaise * machaise;
machaise = malloc(sizeof(struct Chaise));

En C++ on utilise généralement l'opérateur new comme ceci :

Chaise * machaise;
machaise = new Chaise();

On note ici l'utilisation des ( ) après Chaise qui montre clairement qu'on cherche à construire un objet. Une fois la mémoire allouée, le constructeur de la classe est donc automatiquement appelé.

Pour la destruction, delete remplace le free que vous connaissez :

Chaise * machaise;
machaise = new Chaise();
machaise->enleverPied();
delete machaise;
Représentation UML

Je ne vais pas vous faire un cours de modélisation UML mais juste vous présenter une manière graphique de représenter une classe. J'utiliserai ce symbolisme tout au long du tuto pour résumer brièvement les fonctionnalités d'une classe :

Ce qui donne par exemple pour reprendre notre chère chaise :

Principe d'une caméra TrackBall Implémentation de la caméra

Implémentation de la caméra

Quelques bases de C++ Scène de test

Implémentation de la caméra

Nous allons implémenter la caméra TrackBall avec le concept de classe que nous venons de voir.
Nous l'avons vu plus haut il nous faut gérer trois types d'événements :

  • l'appui sur le bouton gauche de la souris : nous n'activerons le mouvement à la souris que si ce bouton est enfoncé ;

  • le mouvement de la souris : pour changer l'orientation de la scène ;

  • l'appui sur la touche HOME pour remettre l'orientation de la scène à sa valeur initiale.

Dans le corps principal de notre programme SDL (partie suivante) nous devrons donc envoyer les événements nécessaires au fonctionnement de la caméra.

Vous savez comment placer une caméra manuellement avec gluLookAt. Ici c'est la méthode look de notre classe TrackBallCamera qui s'occupera d'appeler le gluLookAt pour nous. Dans le code d'affichage de la scène nous n'aurons donc qu'à appeler cette méthode.

Nous allons aussi rajouter deux autres méthodes pour configurer la sensibilité de notre caméra :

  • setMotionSensivity : pour déterminer la vitesse de rotation de la scène en fonction du mouvement en pixel du curseur de la souris ;

  • setScrollSensivity : pour déterminer de combien zoomer/dézoomer lorsque l'on utilise la molette de la souris.

Tout cela se traduit donc de la façon suivante en UML et C++ :

UML simplifié

Déclaration C++

class TrackBallCamera
{
public:
    TrackBallCamera();
    virtual void OnMouseMotion(const SDL_MouseMotionEvent & event);
    virtual void OnMouseButton(const SDL_MouseButtonEvent & event);
    virtual void OnKeyboard(const SDL_KeyboardEvent & event);
    virtual void look();
    virtual void setMotionSensivity(double sensivity);
    virtual void setScrollSensivity(double sensivity);
    virtual ~TrackBallCamera();
protected:
    double _motionSensivity;
    double _scrollSensivity;
    bool _hold;
    double _distance;
    double _angleY;
    double _angleZ;
    SDL_Cursor * _hand1;
    SDL_Cursor * _hand2;
};;

J'en ai profité pour rajouter tous les attributs que nous allons utiliser. Une petite explication s'impose donc :

  • double _motionSensivity : utilisé pour stocker la sensibilité de la caméra aux mouvements de la souris ;

  • double _scrollSensivity : sensibilité de la caméra au scroll de la souris (« pas » d'un déplacement) ;

  • bool _hold : est-on actuellement en train de maintenir le bouton gauche de la souris enfoncé ?

  • double _distance : distance entre la caméra et le centre de la scène ;

  • double _angleY : angle de rotation verticale de la scène (en vert sur le schéma plus haut) ;

  • double _angleZ : angle de rotation horizontale de la scène (donc autour de la verticale, en bleu sur le schéma).

Les deux derniers attributs sont les deux curseurs de la souris que nous utiliserons :

_hand1 en temps normal, _hand2 quand le bouton gauche de la souris est enfoncé.

Vous remarquerez au passage que je précède les attributs de la classe du symbole underscore « _ ». Cela permet dans l'implémentation des méthodes de distinguer plus rapidement variables temporaires (ou paramètres) et attributs. Vous n'êtes bien sûr pas forcés de suivre cette règle.

Constructeur

Dans le constructeur nous allons simplement initialiser tous les attributs à des valeurs initiales connues. Il ne faut rien laisser qui puisse être utilisé sans avoir été initialisé.

La partie la moins évidente est peut-être la création des deux curseurs. Pour faciliter les choses j'ai relégué tout le travail dans une fonction rajoutée à sdlglutils : cursorFromXPM (fournie dans l'archive finale).

TrackBallCamera::TrackBallCamera()
{
    const char *hand1[] =
        {
            /* width height num_colors chars_per_pixel */
            " 16 16 3 1 ",
            /* colors */
            "X c #000000",
            ". c #ffffff",
            "  c None",
            /* pixels */
            "       XX       ",
            "   XX X..XXX    ",
            "  X..XX..X..X   ",
            "  X..XX..X..X X ",
            "   X..X..X..XX.X",
            "   X..X..X..X..X",
            " XX X.......X..X",
            "X..XX..........X",
            "X...X.........X ",
            " X............X ",
            "  X...........X ",
            "  X..........X  ",
            "   X.........X  ",
            "    X.......X   ",
            "     X......X   ",
            "     X......X   ",
            "0,0"
        };
    const char *hand2[] =
        {
            /* width height num_colors chars_per_pixel */
            " 16 16 3 1 ",
            /* colors */
            "X c #000000",
            ". c #ffffff",
            "  c None",
            /* pixels */
            "                ",
            "                ",
            "                ",
            "                ",
            "    XX XX XX    ",
            "   X..X..X..XX  ",
            "   X........X.X ",
            "    X.........X ",
            "   XX.........X ",
            "  X...........X ",
            "  X...........X ",
            "  X..........X  ",
            "   X.........X  ",
            "    X.......X   ",
            "     X......X   ",
            "     X......X   ",
            "0,0"
        };
    _hand1 = cursorFromXPM(hand1); //création du curseur normal
    _hand2 = cursorFromXPM(hand2); //création du curseur utilisé quand le bouton est enfoncé
    SDL_SetCursor(_hand1); //activation du curseur normal
    _hold = false; //au départ on part du principe que le bouton n'est pas maintenu
    _angleY = 0;
    _angleZ = 0;
    _distance = 2; //distance initiale de la caméra avec le centre de la scène
    _motionSensivity = 0.3;
    _scrollSensivity = 1;
}

Comme vous pouvez le voir, le constructeur fait appel à une fonction SDL, SDL_SetCursor, qui ne doit pas être exécutée avant la création de votre fenêtre SDL. De ce fait la caméra ne devra être créée qu'après l'initialisation de l'application.

On Mouse Motion

Cette méthode est la plus importante de la classe et pourtant l'une des plus courtes. Rappelez-vous le principe de la caméra : lorsque le curseur de la souris est bougé, si le bouton gauche de la souris est maintenu appuyé, alors la scène tourne. Voyons donc comment cela se traduit en code :

void TrackBallCamera::OnMouseMotion(const SDL_MouseMotionEvent & event)
{
    if (_hold) //si nous maintenons le bouton gauche enfoncé
    {
        _angleZ += event.xrel*_motionSensivity; //mouvement sur X de la souris -> changement de la rotation horizontale
        _angleY += event.yrel*_motionSensivity; //mouvement sur Y de la souris -> changement de la rotation verticale
        //pour éviter certains problèmes, on limite la rotation verticale à des angles entre -90° et 90°
        if (_angleY > 90)
            _angleY = 90;
        else if (_angleY < -90)
            _angleY = -90;
    }
}
On Mouse Button

Cette méthode nous permet de gérer deux choses :

  • l'appui et le relâchement du bouton gauche de la souris ;

  • le mouvement de la molette de la souris.

Lorsque l'on bouge la molette, deux événements successifs sont générés : SDL_MOUSEBUTTONDOWN et SDL_MOUSEBUTTONUP. Nous n'utiliserons donc que le premier.

void TrackBallCamera::OnMouseButton(const SDL_MouseButtonEvent & event)
{
    if (event.button == SDL_BUTTON_LEFT) //l'événement concerne le bouton gauche
    {
        if ((_hold)&&(event.type == SDL_MOUSEBUTTONUP)) //relâchement alors qu'on était enfoncé
        {
            _hold = false; //le mouvement de la souris ne fera plus bouger la scène
            SDL_SetCursor(_hand1); //on met le curseur normal
        }
        else if ((!_hold)&&(event.type == SDL_MOUSEBUTTONDOWN)) //appui alors qu'on était relâché
        {
            _hold = true; //le mouvement de la souris fera bouger la scène
            SDL_SetCursor(_hand2); //on met le curseur spécial
        }
    }
    else if ((event.button == SDL_BUTTON_WHEELUP)&&(event.type == SDL_MOUSEBUTTONDOWN)) //coup de molette vers le haut
    {
        _distance -= _scrollSensivity; //on zoome, donc rapproche la caméra du centre
        if (_distance < 0.1) //distance minimale, à changer si besoin (avec un attribut par exemple)
            _distance = 0.1;
    }
    else if ((event.button == SDL_BUTTON_WHEELDOWN)&&(event.type == SDL_MOUSEBUTTONDOWN)) //coup de molette vers le bas
    {
            _distance += _scrollSensivity; //on dézoome donc éloigne la caméra
    }
}
OnKeyboard

La dernière méthode qui vient utiliser les événements est la gestion du clavier, pour l'appui sur la touche HOME. On se contente d'y remettre la rotation de la scène à zéro :

void TrackBallCamera::OnKeyboard(const SDL_KeyboardEvent & event)
{
    if ((event.type == SDL_KEYDOWN)&&(event.keysym.sym == SDLK_HOME)) //appui sur la touche HOME
    {
        _angleY = 0; //remise à zéro des angles
        _angleZ = 0;
    }
}
Look

Tout cela est bien beau, nous savons comment changer des variables avec la souris et le clavier mais ça ne fait en rien bouger la caméra dans notre scène. En effet nous n'avons pour l'instant pas vu la moindre commande OpenGL !
Il est donc temps de s'y mettre avec la méthode Look qui viendra remplacer, dans votre fonction d'affichage, l'appel à gluLookAt.

Remplacer gluLookAt ? Parce qu'il y a un truc mieux que tu nous as caché !! ?

Non non. La méthode Look appelle elle-même gluLookAt mais avec des paramètres qui dépendent de la position de la caméra, c'est pour ça que vous n'avez plus à l'appeler vous-mêmes.
Si on se réfère aux schémas en début de chapitre qui expliquent le principe de la caméra TrackBall, on remarque plusieurs choses :

  • elle regarde le centre de la scène ;

  • ce n'est pas la caméra mais la scène qui est tournée autour de Y et Z.

Il suffit alors de traduire tout ça en code :

void TrackBallCamera::look()
{
    gluLookAt(_distance,0,0,
              0,0,0,
              0,0,1); // la caméra regarde le centre (0,0,0) et est sur l'axe X à une certaine distance du centre donc (_distance,0,0)
    glRotated(_angleY,0,1,0); //la scène est tournée autour de l'axe Y
    glRotated(_angleZ,0,0,1); //la scène est tournée autour de l'axe Z
}

Et voilà ce n'était vraiment pas sorcier.
La dernière chose qu'il nous reste à faire dans le code même de la caméra est une destruction propre de ce qui a été alloué dynamiquement.

Destructeur

Les seules choses allouées dynamiquement sont les curseurs qu'il nous faut détruire quand la caméra est détruite :

TrackBallCamera::~TrackBallCamera()
{
    SDL_FreeCursor(_hand1); //destruction du curseur normal
    SDL_FreeCursor(_hand2); //destruction du curseur spécial
    SDL_SetCursor(NULL); //on remet le curseur par défaut.
}

Quelques bases de C++ Scène de test

Scène de test

Implémentation de la caméra Contrôle avancé de la caméra (Partie 2/2)

Scène de test

Le code de la classe TrackBallCamera est complet et n'a besoin de rien de plus. Cependant un objet camera ne va pas recevoir tout seul les événements, il faut les lui donner. Nous allons donc voir avec une petite scène de test simple comment utiliser la caméra que nous venons de créer. Pour faire original et pas du tout inspiré de Google Earth, nous allons créer une sphère avec la texture de de la Terre. Hum hum ! :-°

En variables globales nous allons donc utiliser :

GLuint earth; //l'identifiant de la texture de la Terre
TrackBallCamera * camera; //un pointeur vers notre caméra

Pourquoi pas directement une caméra ?

Rappelez-vous, le constructeur appelle des fonctions SDL qui nécessitent qu'une fenêtre SDL existe déjà. Il ne faut donc pas que la caméra soit construite dès le lancement du programme (ce qui serait le cas ici si nous n'utilisions pas de pointeur). Nous la créons donc dynamiquement après la fenêtre :

atexit(stop); //stop() sera appelé quand on fera exit(0);
//...
        SDL_SetVideoMode(width, height, 32, SDL_OPENGL);
//...
        earth = loadTexture("EarthMap.jpg");
        camera = new TrackBallCamera();
        camera->setScrollSensivity(0.1);

Comme la caméra a été créée dynamiquement c'est à nous de la détruire proprement à la fin de l'exécution du programme. C'est ce qu'on fait dans la fonction stop, appelée quand on fera exit(0); dans le corps du programme :

void stop()
{
    delete camera; //destruction de la caméra allouée dynamiquement
    SDL_Quit();
}

Comme je vous l'ai dit plus haut, la caméra ne recevra pas les événements clavier/souris si on ne les lui donne pas. C'est pourquoi dans notre partie de gestion des événements, il faut donner à la caméra les événements dont on ne se sert pas :

while(SDL_PollEvent(&event))
        {
            switch(event.type)
            {
                case SDL_QUIT:
                exit(0);
                break;
                case SDL_KEYDOWN:
                switch (event.key.keysym.sym)
                {
                    case SDLK_p:
                    takeScreenshot("test.bmp");
                    break;
                    case SDLK_ESCAPE:
                    exit(0);
                    break;
                    default : //on a utilisé la touche P et la touche ECHAP, le reste est donné à la caméra
                    camera->OnKeyboard(event.key);
                }
                break;
                case SDL_MOUSEMOTION: //la souris est bougée, ça n'intéresse que la caméra
                camera->OnMouseMotion(event.motion);
                break;
                case SDL_MOUSEBUTTONUP:
                case SDL_MOUSEBUTTONDOWN:
                camera->OnMouseButton(event.button); //tous les événements boutons (up ou down) sont donnés à la caméra
                break;
            }
        }

La dernière chose qu'il nous reste à faire est de dessiner la scène, ici très basique.

On n'appelle plus gluLookAt nous-mêmes mais bien la méthode Look de notre objet camera.

void DrawGL()
{
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity( );
    camera->look();
    GLUquadric* params = gluNewQuadric();
    gluQuadricTexture(params,GL_TRUE);
    glBindTexture(GL_TEXTURE_2D,earth);
    gluSphere(params,1,20,20);
    gluDeleteQuadric(params);
    glFlush();
    SDL_GL_SwapBuffers();
}

Téléchargez la vidéo au format avi/Xvid (1.17 Mo)

Téléchargez le projet Code::Blocks, l'exécutable Windows et le Makefile Unix (1.30 Mo)

Et voilà finie la prise de tête de prévoir à l'avance comment placer la caméra pour bien voir votre scène !

Dans la chapitre suivant nous verrons une caméra encore plus intéressante mais un peu plus dure à implémenter : la caméra FreeFly qui nous permettra de voler librement dans notre scène.

Implémentation de la caméra Contrôle avancé de la caméra (Partie 2/2)

Contrôle avancé de la caméra (Partie 2/2)

Scène de test Principe d'une caméra FreeFly

Dans cette seconde partie sur le contrôle avancé de la caméra, nous allons voir un autre type de caméra : la caméra FreeFly. Nous ne verrons pas de nouveau concept C++ mais cette caméra requiert un peu plus de maths que la précédente. Heureusement pour vous j'ai créé une annexe mathématique rien que pour ce chapitre mais qui peut vous être utile tout le temps : la trigonométrie.
Encore une fois deux lectures sont possibles pour ce chapitre donc ne paniquez pas si les mathématiques utilisées vous dépassent. Il est tout à fait possible de voir la caméra uniquement comme une nouvelle fonctionnalité. Comprendre son principe et son utilisation dans une scène de test est alors amplement suffisant.

Principe d'une caméra FreeFly

Contrôle avancé de la caméra (Partie 2/2) Gestion fluide du mouvement

Principe d'une caméra FreeFly

Une caméra FreeFly, comme son nom l'indique, permet de se déplacer/voler librement dans une scène. On retrouve parfois ce type de caméra sous des appellations incomplètes comme FreeLook, voire erronée comme FPS (First Person Shooter). FreeLook ne donne pas la même idée de déplacement, caractéristique importante de la caméra FreeFly. Une caméra FPS quant à elle est plus complexe car assujettie à la gravité, à des déplacements contraints (celui du personnage que l'on incarne), etc.

Quoi qu'il en soit, le principe est exactement le même que dans Counter Strike, quand on est mort (certains plus souvent que d'autres ;) ) et qu'on se balade librement dans la map pour regarder la fin de la partie.

Caméra FreeFly (FreeLook) dans Counter Strike

Gestion du regard à la souris

Pour pouvoir regarder tout autour de nous, nous allons utiliser les mouvements de la souris pour orienter la caméra :

  • un mouvement horizontal de la souris fait tourner la caméra horizontalement autour de la verticale du monde (regard à gauche et à droite) ;

  • un mouvement vertical de la souris fait tourner la caméra verticalement (le regard se lève ou se baisse).

Ces mouvements sont illustrés par les schémas ci-dessous (réutilisation des noms des angles propres aux coordonnées sphériques) :

Mouvement horizontal de la souris (schéma vu du dessus)

Mouvement vertical de la souris (schéma vu de côté)

Gestion du déplacement au clavier

La souris nous permet d'orienter la caméra par rapport aux axes X, Y, Z locaux, le clavier quant à lui va nous permettre de déplacer la caméra selon l'orientation actuelle :

  • 2 touches nous permettent de faire avancer/reculer la caméra ;

  • 2 touches nous permettent de faire strafer (déplacement latéral) la caméra.

J'ai choisi d'utiliser les touches ZQSD, utilisées de façon presque standard pour ce genre de mouvement sur un clavier AZERTY :

Mouvement avant/arrière

Contrôle avancé de la caméra (Partie 2/2) Gestion fluide du mouvement

Gestion fluide du mouvement

Principe d'une caméra FreeFly Implémentation de la caméra

Gestion fluide du mouvement

Pour l'instant en suivant mon tutoriel, nous avons utilisé les événements pour :

  • faire bouger notre grue dans le chapitre sur les transformations ;

  • faire tourner la caméra dans le chapitre précédent sur la caméra TrackBall.

Nous utiliserons le même système de mouvement à la souris, donc l'utilisation des SDL_MouseMotionEvent pour orienter la caméra.
Pour notre grue, la ruse de l'animation était basée sur l'activation de la répétition des touches avec SDL_EnableKeyRepeat. La vitesse du mouvement était donc dépendante de la vitesse de répétition des touches, ce qui pose trois problèmes :

  • si le taux de répétition des touches est trop faible le mouvement est saccadé ;

  • utiliser l'événement d'appui sur une touche à son apparition empêche de pouvoir utiliser plusieurs touches en même temps ;

  • le mouvement n'est pas réellement lié au temps écoulé.

Dans le cadre de notre petite grue sans prétention tout cela était tout à fait tolérable, ici nous rentrons dans un domaine différent : le contrôle de la vue 3D. Les personnes habituées aux FPS ne se rendent peut-être pas compte que la manière avec laquelle ils se déplacent peut vite donner la nausée à un spectateur non habitué. La fluidité du mouvement de la caméra est donc primordiale ici.

Les KeyStates

Je vous l'ai dit juste avant, utiliser l'événement clavier juste à son apparition n'est pas la bonne solution pour gérer un déplacement fluide.
Nous allons donc nous contenter de mettre à jour un tableau interne de l'état des touches qui nous intéressent.

Par exemple si la touche SDLK_z nous intéresse et que l'on reçoit un événement SDL_KEYDOWN, nous mettrons la case SDLK_z de notre tableau de booléens à true, pour indiquer qu'elle est actuellement enfoncée.
De même sur la réception d'un événement SDL_KEYUP avec la touche SDLK_z, nous mettrons la case correspondante du tableau à false pour indiquer qu'elle n'est pas/plus enfoncée.

Comment faire correspondre un nom de touche avec un indice dans un tableau ?

Nous allons pour cela utiliser ce que l'on appelle, comme en PHP, un tableau associatif. Au lieu de faire mon_tableau[indice_numérique] nous ferons mon_tableau_associatif[clé]. L'outil en C++ qui permet de faire cela est appelé une map. On la définit par les deux types qu'elle utilise, le type pour la clé (la clé est ce qui remplace l'indice numérique, bien qu'elle puisse être numérique aussi :) ), et le type de la valeur à stocker.
Dans notre cas, nous souhaitons stocker des booléens (bool) indiquant si une touche (SDLKey) est actuellement enfoncée ou non.
Cela correspond donc à la map suivante :

typedef std::map<SDLKey,bool> KeyStates;

On peut l'utiliser ainsi :

Keystates keystates; //déclaration d'une variable de type KeyStates
keystates[SDLK_Z] = true; //association bidon, eh oui je n'ai pas recu d'événement comment je peux savoir ? :p )
Configuration du clavier

Dans l'absolu on se fiche pas mal que la touche pour faire avancer soit la touche SDLK_z. Ça pourrait être SDLK_i qu'on s'en ficherait tout autant. :-°
Ce que l'on veut savoir à n'importe quel moment c'est si la touche qui sert à faire avancer en avant (forward) est enfoncée ou non, quel que soit le choix du joueur/concepteur en ce qui concerne la configuration des touches.

Nous allons donc utiliser un deuxième tableau associatif qui associera une action et la touche utilisée pour effectuer cette action. L'action sera nommée textuellement, comme « forward » pour aller en avant, et la touche sera le code de la SDLKey comme SDLK_z (qui est en fait une valeur numérique mais on n'a pas besoin de le savoir).
Cela correspond donc à la map suivante :

typedef std::map<std::string,SDLKey> KeyConf;

On peut alors l'utiliser ainsi :

KeyConf keyconf; //déclaration d'une variable de type KeyConf.
keyconf["forward"] = SDLK_z; //ici je choisis d'utiliser la touche z pour l'action forward.

On peut alors tout à fait imaginer charger la configuration des touches via un fichier de configuration ou même demander à l'utilisateur quelles touches utiliser. Ces deux techniques ne seront pas enseignées ici car dépassent le cadre du tuto OpenGL. Nous utiliserons juste une affectation action/touche comme dans l'exemple du dessus.

Grâce à la combinaison de KeyStates et de notre configuration des touches, il est alors facile de définir si la touche pour réaliser une action donnée est enfoncée :

Keystates keystates;
KeyConf keyconf;
keyconf["forward"] = SDLK_z;
keystates[keyconf["forward"]] = true;

La partie keyconf["forward"] vient récupérer la touche utilisée pour l'action « forward », cette touche sert de clé pour la map touche/booléen keystates.

Mouvement animé

Nous avons déjà réalisé un mouvement animé dès notre première scène 3D avec un cube.
La boucle principale de notre programme était la suivante :

Structure de la boucle principale

Ici nous ferons la même chose : un mouvement de la caméra qui dépend du temps écoulé mais aussi de l'état des touches.
Par exemple nous ne ferons avancer la caméra que si la touche correspondante est enfoncée, ce qui donne en code :

if (keystates[keyconf["forward"]])
        position += forward * (speed * timestep);

où timestep est le temps écoulé depuis la dernière image, speed est la vitesse de déplacement, forward est le vecteur d'orientation de la caméra (qui pointe là où elle regarde), et position est la position de la caméra.

Vector3D

Mathématiquement je sais ce qu'est un vecteur mais ça existe en C++ ?

À la base non. Mais rien ne nous empêche de le créer. D'ailleurs si vous avez déjà fait un peu de SDL, j'imagine que vous avez pu être amenés à créer des struct ayant comme attributs x et y les positions de vos sprites à blitter.
Quoiqu'il en soit ici des attributs (X,Y et Z) ne nous suffisent pas. Comme on l'a vu dans le code juste au-dessus, il nous faut pouvoir multiplier un vecteur par un nombre, additionner deux vecteurs, et un peu plus encore. Pour cela on utilise en C++ la surcharge d'opérateurs qui permet de redéfinir les opérations élémentaires comme l'addition, la multiplication pour un type (ici notre classe vecteur) non élémentaire.

En définissant ainsi l'opérateur + comme méthode de notre classe, il est alors tout à fait possible dans le code, comme on le fait pour des nombres, de faire vecteur1 + vecteur2. Et mine de rien ça simplifiera beaucoup le code de la caméra.

Voici la déclaration de la classe Vector3D que nous allons utiliser. Je vous la donne pour vous montrer la déclaration des opérateurs. L'implémentation en elle-même est triviale, car purement mathématique et je laisse les curieux aller la lire dans l'archive finale. Pour l'utiliser nous n'avons pas besoin de connaître les détails de l'implémentation.

class Vector3D
{
public:
    double X;
    double Y;
    double Z;
    Vector3D();
    Vector3D(double x,double y,double z);
    Vector3D(const Vector3D & v);
    Vector3D(const Vector3D & from,const Vector3D & to);
    Vector3D & operator= (const Vector3D & v);
    Vector3D & operator+= (const Vector3D & v);
    Vector3D operator+ (const Vector3D & v) const;
    Vector3D & operator-= (const Vector3D & v);
    Vector3D operator- (const Vector3D & v) const;
    Vector3D & operator*= (const double a);
    Vector3D operator* (const double a)const;
    friend Vector3D operator* (const double a,const Vector3D & v);
    Vector3D & operator/= (const double a);
    Vector3D operator/ (const double a)const;
    Vector3D crossProduct(const Vector3D & v)const;
    double length()const;
    Vector3D & normalize();
};
Fluidité VS Molette

Il me reste une dernière chose à couvrir avant d'entrer dans l'implémentation de la caméra et cela concerne la molette. Vous l'avez vu dans la chapitre précédent, la molette se gère comme un bouton et on détecte que la molette a été montée si on reçoit un événement de type SDL_MOUSEBUTTONDOWN sur le bouton SDL_BUTTON_WHEELUP. Par conséquent si on bougeait sur cet événement on viendrait remettre en cause toute ma super théorie de fluidité quantique à convergence rétroactive ! Image utilisateur

Nous allons donc faire comme avec le clavier :

  • sur détection d'un mouvement de la molette on met un booléen à true ;

  • quand nous faisons bouger la caméra, si ce booléen vaut true nous déplaçons verticalement la caméra en fonction du temps écoulé.

Oui mais quand remet-on ce booléen à false ?

On ne peut pas le remettre à false quand on détecte que la molette ne bouge plus car les événements SDL_MOUSEBUTTONDOWN et SDL_MOUSEBUTTONUP pour la molette sont consécutifs, on n'aurait donc aucun mouvement. On utilise donc le temps !

  • Sur détection d'un mouvement de la molette on met un booléen à true et une variable temps restant à 250 (ms, c'est un exemple).

  • Quand nous faisons bouger la caméra, si ce booléen vaut true :

    • on décrémente le temps restant du temps écoulé ;

    • si le temps restant est < 0 alors on passe le booléen à false.

Nous allons bien sûr voir toutes ces techniques en pratique, appliquées à notre caméra FreeFly.

Principe d'une caméra FreeFly Implémentation de la caméra

Implémentation de la caméra

Gestion fluide du mouvement Scène de test

Implémentation de la caméra

Tout comme notre caméra TrackBall du chapitre précédent, nous allons implémenter la caméra FreeFly en C++ à l'aide d'une classe.

Nous devons ici aussi gérer trois types d'événements :

  • le mouvement de la souris : pour changer l'orientation de la caméra ;

  • le clavier : pour déplacer la caméra ;

  • la molette (qui se gère comme un clic de bouton) : pour faire monter/descendre la caméra.

Nous aurons une méthode look dont l'appel viendra remplacer dans nos codes l'utilisation du gluLookAt (appelé en interne par la méthode look de toute façon).

Nous allons aussi créer trois méthodes pour paramétrer la caméra :

  • setSpeed : pour définir la vitesse de déplacement de la caméra ;

  • setSensivity : pour déterminer la vitesse de rotation de la caméra en fonction du mouvement en pixels du curseur de la souris ;

  • setPosition : pour placer précisément la caméra quand on le souhaite.

Nous introduirons aussi une dernière méthode, animate, qui viendra gérer le mouvement fluide de la caméra, comme expliqué dans la partie précédente.

Voici une traduction formelle de ce que je viens de dire rapidement :

UML simplifié

Déclaration C++

class FreeFlyCamera
{
public:
    FreeFlyCamera(const Vector3D & position = Vector3D(0,0,0));
    virtual void OnMouseMotion(const SDL_MouseMotionEvent & event);
    virtual void OnMouseButton(const SDL_MouseButtonEvent & event);
    virtual void OnKeyboard(const SDL_KeyboardEvent & event);
    virtual void animate(Uint32 timestep);
    virtual void setSpeed(double speed);
    virtual void setSensivity(double sensivity);
    virtual void setPosition(const Vector3D & position);
    virtual void look();
    virtual ~FreeFlyCamera();
protected:
    double _speed;
    double _sensivity;
    Uint32 _timeBeforeStoppingVerticalMotion;
    bool _verticalMotionActive;
    int _verticalMotionDirection;
    typedef std::map<SDLKey,bool> KeyStates;
    KeyStates _keystates;
    typedef std::map<std::string,SDLKey> Keyconf;
    Keyconf _keyconf;
    Vector3D _position;
    Vector3D _target;
    Vector3D _forward;
    Vector3D _left;
    double _theta;
    double _phi
    void VectorsFromAngles();
};

Décryptons ensemble tous les attributs dont nous allons avoir besoin :

  • double _speed : vitesse de déplacement de la caméra ;

  • double _sensivity : sensibilité de la caméra aux mouvements de la souris ;

  • Uint32 _timeBeforeStoppingVerticalMotion : si un mouvement vertical (à la molette) est en cours, combien de temps reste-t-il avant de l'arrêter ?

  • bool _verticalMotionActive : y a-t-il un mouvement vertical en cours ?

  • int _verticalMotionDirection : sens du mouvement vertical quand il a été déclenché à la molette (+1 vers la haut, -1 vers le bas) ;

  • KeyStates _keystates : tableau donnant l'état des touches utilisées ;

  • Keyconf _keyconf : tableau donnant les touches à utiliser pour chaque action ;

  • Vector3D _position : position de la caméra dans l'absolu ;

  • Vector3D _target : point regardé par la caméra dans l'absolu ;

  • Vector3D _forward : vecteur donnant la direction du regard (et donc du déplacement vers l'avant) ;

  • Vector3D _left : vecteur perpendiculaire au regard pour le déplacement latéral ;

  • double _theta : angle de rotation horizontale de la caméra (autour de la verticale) ;

  • double _phi : angle de rotation verticale de la caméra.

On remarque aussi, dans le schéma UML comme dans la déclaration de la classe, une méthode protégée (non publique) : VectorsFromAngles. Cette méthode viendra calculer les vecteurs _forward et _left en fonction des nouvelles valeurs de _theta et _phi.

Constructeur

Vous l'avez vu dans l'implémentation, le constructeur possède un paramètre facultatif : la position initiale de la caméra. Si on ne la spécifie pas, elle sera en (0,0,0) au départ.
Le code n'est pas passionnant, juste de l'initialisation des attributs :

FreeFlyCamera::FreeFlyCamera(const Vector3D & position)
{
    _position = position; //si aucune position n'est définie on reçoit quand même (0,0,0) en paramètre
    _phi = 0;
    _theta = 0;
    VectorsFromAngles(); //décrit un peu plus loin
    _speed = 0.01;
    _sensivity = 0.2;
    _verticalMotionActive = false;
//Initialisation de la configuration des touches
    _keyconf["forward"] = SDLK_z;
    _keyconf["backward"] = SDLK_s;
    _keyconf["strafe_left"] = SDLK_q;
    _keyconf["strafe_right"] = SDLK_d;
    _keyconf["boost"] = SDLK_LSHIFT;
//Initialisation des KeyStates
    _keystates[_keyconf["forward"]] = false;
    _keystates[_keyconf["backward"]] = false;
    _keystates[_keyconf["strafe_left"]] = false;
    _keystates[_keyconf["strafe_right"]] = false;
    _keystates[_keyconf["boost"]] = false;
    SDL_WM_GrabInput(SDL_GRAB_ON);
    SDL_ShowCursor(SDL_DISABLE);
}

Pourquoi ces deux dernières lignes ?

Le but de notre caméra est de bouger le regard avec la souris en permanence. Nous utilisons donc l'information de déplacement relatif de la souris pour faire tourner d'autant la caméra. Que se passe-t-il si la souris quitte la fenêtre et re-rentre par un autre endroit ?

  1. Tout le temps où la souris sera en dehors de la fenêtre la caméra sera indirigeable, ça peut être pénible quand on est pas loin du bord.

  2. Si la caméra revient dans la fenêtre par un autre côté, le déplacement relatif peut être énorme d'un coup et faire trop tourner la caméra, on ne comprendra plus ce que l'on regarde (changement trop brusque).

Par la combinaison des deux lignes :

SDL_WM_GrabInput(SDL_GRAB_ON);
    SDL_ShowCursor(SDL_DISABLE);

on vient demander à SDL d'interdire à la souris de quitter la fenêtre, mais en plus, quand le curseur est juste au bord de la fenêtre, de générer quand même des informations de déplacement relatif de la souris.

Ces deux commandes appelées par le constructeur de notre caméra nécessitent que la SDL soit initialisée. Il ne faut donc pas construire un objet FreeFlyCamera avant. Dans notre scène de test comme nous utiliserons la caméra en variable globale pour l'instant, nous utiliserons l'allocation dynamique (avec un new) pour pallier ce problème.

On Mouse Motion

Comme notre caméra est assurée de recevoir des événements de mouvement de la souris corrects, la méthode OnMouseMotion est très simple :

void FreeFlyCamera::OnMouseMotion(const SDL_MouseMotionEvent & event)
{
    _theta -= event.xrel*_sensivity;
    _phi -= event.yrel*_sensivity;
    VectorsFromAngles();
}

Pourquoi des signes « - » ?

Les angles s'expriment dans le sens trigonométrique (sens inverse des aiguilles d'une montre). Un changement positif de _theta correspond donc à un mouvement « vers la gauche » de la souris, soit un déplacement négatif sur l'axe des X. Soustraire un nombre négatif revient à additionner son opposé donc nous retombons sur nos pattes.
De même pour _phi, un changement positif correspond à un mouvement « vers le haut » de la souris, soit un déplacement négatif sur l'axe des Y (pour SDL).

Vectors From Angles

À chaque fois que les angles sont changés, il faut recalculer le vecteur d'orientation de la caméra, _forward, ainsi que le vecteur latéral _left.
Le vecteur _forward permet de définir à la fois où regarder, _target, et dans quelle direction avancer. Le vecteur _left sert pour le mouvement latéral.

Comme ce sont des vecteurs, ils donnent des directions et ne sont donc pas localisés à un endroit précis de l'espace. On peut donc réfléchir comme si nous étions en (0,0,0). De deux angles on veut passer aux coordonnées 3D d'un vecteur, hum... :-° mais comme c'est bizarre... ça me fait penser étrangement aux coordonnées sphériques de l' annexe de trigonométrie. Comme c'est bizarre et pas du tout intentionnel... :soleil:

Rappel de l'utilisation des angles en coordonnées sphériques

Nous appliquerons donc le calcul bête et méchant expliqué en annexe pour calculer le vecteur _forward (équivalent du vecteur rouge sur le dessin du dessus). Ici nous réfléchissons sur des vecteurs unitaires (de longueur 1) donc le rayon de la « sphère » vaut 1.

void FreeFlyCamera::VectorsFromAngles()
{
    static const Vector3D up(0,0,1); //une constante, le vecteur vertical du monde, utilisé dans les calculs
//On limite les valeurs de _phi, on vole certes, mais on en fait pas de loopings :p
    if (_phi > 89)
        _phi = 89;
    else if (_phi < -89)
        _phi = -89;
//passage des coordonnées sphériques aux coordonnées cartésiennes
    double r_temp = cos(_phi*M_PI/180);
    _forward.Z = sin(_phi*M_PI/180);
    _forward.X = r_temp*cos(_theta*M_PI/180);
    _forward.Y = r_temp*sin(_theta*M_PI/180);
//diantre mais que fait ce passage ?
    _left = up.crossProduct(_forward);
    _left.normalize();
//avec la position de la caméra et la direction du regard, on calcule facilement ce que regarde la caméra (la cible)
    _target = _position + _forward;
}}

Le seul point flou qui peut subsister ici est le calcul du vecteur _left pour le mouvement latéral.
On utilise ici le produit vectoriel (méthode crossProduct de la classe Vector3D) pour calculer le vecteur _left, perpendiculaire au plan formé par le vecteur _forward avec la verticale. Ce vecteur doit ensuite être normalisé (on lui donne une longueur de 1) pour pouvoir être utilisé dans nos mouvements.
Tout cela justifie donc les deux lignes :

_left = up.crossProduct(_forward);
    _left.normalize();

_left est perpendiculaire au plan formé par_forward avec la verticale

On Mouse Button

La gestion de la molette est assez simple. Comme il a été vu dans la partie « Gestion fluide du mouvement », nous ne bougerons pas la caméra directement mais signalerons juste qu'il faut bouger.
J'ai choisi de faire monter/descendre la caméra pendant 250 ms. Ce temps n'est pas à changer en fonction de la vitesse de la caméra, c'est le mouvement lui-même qui utilisera l'information de vitesse.

void FreeFlyCamera::OnMouseButton(const SDL_MouseButtonEvent & event)
{
    if ((event.button == SDL_BUTTON_WHEELUP)&&(event.type == SDL_MOUSEBUTTONDOWN)) //coup de molette vers le haut
    {
        _verticalMotionActive = true; //on demande à activer le mouvement vertical
        _timeBeforeStoppingVerticalMotion = 250; //pendant 250 ms
        _verticalMotionDirection = 1; //et vers le haut
    }
    else if ((event.button == SDL_BUTTON_WHEELDOWN)&&(event.type == SDL_MOUSEBUTTONDOWN)) //coup de molette vers le bas
    {
        _verticalMotionActive = true; //on demande à activer le mouvement vertical
        _timeBeforeStoppingVerticalMotion = 250; //pendant 250 ms
        _verticalMotionDirection = -1; //et vers le bas
    }
}
On Keyboard

Dans la méthode OnKeyBoard, on va utiliser les KeyStates découverts plus haut et initialisés dans le constructeur. On pourrait utiliser des if, ou un switch...case, mais il y a plus simple :

void FreeFlyCamera::OnKeyboard(const SDL_KeyboardEvent & event)
{
//on parcourt tous les keystates actuels
    for (KeyStates::iterator it = _keystates.begin();it != _keystates.end();
            it++)
    {
        if (event.keysym.sym == it->first) //est-ce que la touche responsable de l'événement est celle du keystate ?
        {
            it->second = (event.type == SDL_KEYDOWN); //true si enfoncé, false si relâché
            break; //la touche responsable de l'événement a été utilisée, on quitte le for
        }
    }
}

La seule difficulté ici est le parcours des KeyStates, le tableau associatif qui donne l'état des touches utilisées.
L'avantage de cette technique : code très court, simple, et je n'ai pas codé en dur quelles touches tester (pas d'informations redondantes).

Animate

Jusqu'à présent nous avons vu des méthodes qui étaient appelées sur des événements souris/clavier. Ces méthodes (à part le MouseMotion) ne venaient que définir des booléens pour une gestion fluide du mouvement. Mouvement qui va réellement s'effectuer ici, dans la méthode Animate, appelée à chaque boucle du programme. Le code n'est qu'une simple application des principes vus dans la partie « Gestion fluide du mouvement » :

void FreeFlyCamera::animate(Uint32 timestep)
{
//la vitesse réelle du déplacement est soit la vitesse de croisière, soit 10*la vitesse, en fonction
//de l'état enfoncé ou non de la touche correspondant à l'action "boost"
    double realspeed = (_keystates[_keyconf["boost"]])?10*_speed:_speed;
    if (_keystates[_keyconf["forward"]])
        _position += _forward * (realspeed * timestep); //on avance
    if (_keystates[_keyconf["backward"]])
        _position -= _forward * (realspeed * timestep); //on recule
    if (_keystates[_keyconf["strafe_left"]])
        _position += _left * (realspeed * timestep); //on se déplace sur la gauche
    if (_keystates[_keyconf["strafe_right"]])
        _position -= _left * (realspeed * timestep); //on se déplace sur la droite
    if (_verticalMotionActive)
    {
        if (timestep > _timeBeforeStoppingVerticalMotion)
            _verticalMotionActive = false;
        else
            _timeBeforeStoppingVerticalMotion -= timestep;
        _position += Vector3D(0,0,_verticalMotionDirection*realspeed*timestep); //on monte ou on descend, en fonction de la valeur de _verticalMotionDirection
    }
    _target = _position + _forward; //comme on a bougé, on recalcule la cible fixée par la caméra
}

On remarque que dans certains cas, il est possible lors du même appel à Animate d'avancer et reculer en même temps (si les touches « forward » et « backward » sont pressées simultanément). De manière tout à fait logique les deux mouvements s'annulent et la caméra ne bougera pas (en avant ou en arrière).

Look

La dernière méthode intéressante est bien sûr la méthode Look, appelée à chaque image, avant de dessiner la scène. Rien de compliqué ici, les autres méthodes ont calculé pour nous la position de la caméra et la cible qu'elle regarde. Il suffit de faire un appel propre à gluLookAt et tout va pour le mieux dans le meilleur des mondes.

void FreeFlyCamera::look()
{
    gluLookAt(_position.X,_position.Y,_position.Z,
              _target.X,_target.Y,_target.Z,
              0,0,1);
}
Destructeur

On finit néanmoins avec le destructeur pour « remettre les choses comme on les a trouvées ». Si l'utilisateur pense qu'il n'a plus besoin de sa caméra, il serait de bon ton quand même de lui rendre son curseur que l'on a masqué pour nos besoins personnels :

FreeFlyCamera::~FreeFlyCamera()
{
    SDL_WM_GrabInput(SDL_GRAB_OFF);
    SDL_ShowCursor(SDL_ENABLE);
}

Vous trouverez bien sûr l'implémentation complète de la caméra dans l'archive en fin de chapitre.

Gestion fluide du mouvement Scène de test

Scène de test

Implémentation de la caméra La trigonométrie

Scène de test

Voilà, la caméra est prête mais une fois encore, comme pour la caméra TrackBall, il faut évidemment s'assurer de bien la créer et de lui envoyer tous les événements dont elle a besoin.

Un dilemme cependant se présentait à moi à la préparation de ce chapitre. Quelle scène minable allais-je encore créer pour montrer l'intérêt d'une caméra FreeFly ? :euh:
Pour vous épargner une réapparition de la scène du chapitre des textures (sol + cube + pyramide) j'ai lancé un appel sur les forums.
Deux zéros, qui ont appris l'OpenGL grâce à ce cours, ont répondu présents et nous ont donc préparé une petite scène sympathique qui nous servira même dans les chapitres à venir. Merci donc à TheDead Master et à 42 !

Leur scène est incluse dans un fichier à part, scene.cpp, dont les fonctions seront bien sûr appelées à partir de main.cpp.

Leurs fonctions que l'on doit appeler sont :

void chargerTextures(); //pour initialiser les textures que la scène utilise
void dessinerScene(); //pour dessiner leur scène

Nous pouvons donc nous concentrer sur la création de la caméra.
En variable globale nous aurons donc :

FreeFlyCamera * camera;

Encore une fois, sous forme de pointeur pour ne pas la créer avant que la SDL ne soit initialisée. Nous allouons la caméra dynamiquement après la création de la fenêtre :

int main(int argc, char *argv[])
{
//...
    atexit(stop);
//...
    SDL_SetVideoMode(width, height, 32, SDL_OPENGL);
//...
    chargerTextures(); //nécessaire pour que les textures de leur scène soient chargées
    camera = new FreeFlyCamera(Vector3D(0,0,2)); //pour les besoins de la scène on surélève la caméra dès le départ
//...
}

La caméra sera automatiquement détruite à la fin du programme grâce à la fonction stop :

void stop()
{
    delete camera;
    SDL_Quit();
}

Une fois la caméra créée, dans notre boucle principale il ne faut pas oublier :

  • d'envoyer les événements que la caméra attend ;

  • d'appeler la méthode FreeFlyCamera::animate pour que la caméra bouge.

Cela se traduit donc par le code suivant :

while(SDL_PollEvent(&event))
        {
            switch(event.type)
            {
                case SDL_QUIT:
                exit(0);
                break;
                case SDL_KEYDOWN:
                switch (event.key.keysym.sym)
                {
                    case SDLK_p:
                    takeScreenshot("test.bmp");
                    break;
                    case SDLK_ESCAPE:
                    exit(0);
                    break;
                    default : //on a utilisé la touche P et la touche ECHAP, le reste (en keydown) est donné à la caméra
                    camera->OnKeyboard(event.key);
                }
                break;
                case SDL_KEYUP: //on n'utilise pas de keyup, on donne donc tout à la caméra
                camera->OnKeyboard(event.key);
                break;
                case SDL_MOUSEMOTION: //la souris est bougée, ça n'intéresse que la caméra
                camera->OnMouseMotion(event.motion);
                break;
                case SDL_MOUSEBUTTONUP:
                case SDL_MOUSEBUTTONDOWN: //tous les évenements boutons (up ou down) sont donnés à la caméra
                camera->OnMouseButton(event.button);
                break;
            }
        }
        current_time = SDL_GetTicks();
        elapsed_time = current_time - last_time; //on calcule le temps écoulé depuis la dernière image
        last_time = current_time;
        camera->animate(elapsed_time); //et on fait bouger la caméra
        DrawGL();

Le détail de la fonction d'affichage ne nous intéresse pas vraiment ici. En effet la majeure partie se fait dans le scene.cpp pour utiliser la scène de The Dead Master et 42. Par contre, il ne faut pas oublier d'appeler la méthode look() de notre caméra :

void DrawGL()
{
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity( );
    camera->look(); //remplace un appel manuel à gluLookAt
    dessinerScene();
    glFlush();
    SDL_GL_SwapBuffers();
}

Et voilà le travail ! :soleil:

Téléchargez la vidéo au format avi/Xvid (2.11 Mo)

Téléchargez le projet Code::Blocks, l'exécutable Windows et le Makefile Unix (1.23 Mo)

On arrive enfin au bout de ce chapitre ! :pirate:
Rappelez-vous que si vous n'avez pas tout compris des détails de l'implémentation de la caméra, comprendre son principe (mouvement) et comment l'utiliser peut suffire.
Maintenant grâce à notre caméra FreeFly nous pouvons enfin nous balader librement dans nos scènes et les contempler sous vraiment tous les angles.

Nous garderons cette scène pour le prochain chapitre, la transparence, dans lequel nous verrons comment rendre l'eau et les fenêtres transparentes. :magicien: Tout un programme !

Implémentation de la caméra La trigonométrie

La trigonométrie

Scène de test Trigo dans un triangle rectangle

Dans cette annexe mathématique nous verrons le concept mathématique de trigonométrie, généralement rencontré dès la classe de 3e dans le cursus français.
Nous nous contenterons de parcourir le minimum utile pour faire de la 3D et nous verrons que des outils mathématiques faciles d'utilisation peuvent nous être grandement utiles.

Trigo dans un triangle rectangle

La trigonométrie Le cercle trigonométrique

Trigo dans un triangle rectangle

La trigonométrie (littéralement « mesures » dans le « triangle »), est une branche des maths qui vient donner des relations entre les longueurs des côtés d'un triangle et ses angles.

La trigonométrie se rencontre généralement dans le cas d'un triangle rectangle :

En considérant l'angle alpha, on définit deux fonctions « cosinus » et « sinus » permettant de donner une relation entre la valeur de cet angle, et les longueurs des côtés du triangle :

En utilisant l'inverse de ces fonctions on peut alors trouver la valeur de l'angle en fonction des longueurs des côtés. On peut tout aussi bien, à partir de la valeur de l'angle et de la longueur d'un seul côté du triangle, trouver les longueurs des autres côtés.

La trigonométrie Le cercle trigonométrique

Le cercle trigonométrique

Trigo dans un triangle rectangle Systèmes de coordonnées

Le cercle trigonométrique

Le triangle rectangle est la première approche que l'on a de la trigonométrie. On introduit généralement un autre objet mathématique : le cercle trigonométrique. Ce cercle mesure « 1 » (pas d'unité requise) de rayon et nous permet de donner tout leur sens au cosinus et sinus que nous venons de rencontrer :

Dans la figure ci-dessus on retrouve un triangle rectangle et donc nos formules vues plus haut sont toujours applicables :

Et là tout s'éclaire ! Alors qu'auparavant nous pensions que cos et sin étaient de simples mesures abstraites qui ne servaient pas à grand chose, ici on se rend compte grâce au cercle trigonométrique qu'elles permettent de donner l'abscisse (sur X) et l'ordonnée (sur Y) d'un point quelconque du cercle trigonométrique se trouvant à un angle alpha donné par rapport à l'origine.
On note au passage que les angles se mesurent ici dans le sens trigonométrique (c'est-à-dire le sens inverse des aiguilles d'une montre).

dans un triangle rectangle Systèmes de coordonnées

Systèmes de coordonnées

Le cercle trigonométrique Les matrices

Systèmes de coordonnées

Nous avons vu rapidement dans le cas du cercle trigonométrique que nous pouvions passer d'un angle à une coordonnée (X,Y). Il est donc temps de faire un petit rappel non exhaustif sur les systèmes de coordonnées qui nous intéressent avec OpenGL.

Coordonnées cartésiennes

Les coordonnées cartésiennes sont les plus courantes. C'est d'ailleurs dans ce système que l'on exprime les positions de nos vertices avec OpenGL : glVertex3d( X, Y, Z);

Coordonnées polaires

Les coordonnées polaires sont un autre système de coordonnées que nous n'utiliserons pas trop mais qui nous sert d'intermédiaire pour bien comprendre la suite.
Un point est repéré par un angle (thêta) et un rayon (r). On y voit une généralisation du cercle trigonométrique (où r valait 1) et donc nous pouvons très facilement en déduire comment passer des coordonnées polaires aux coordonnées cartésiennes :

Exemple en OpenGL

Nous venons de voir qu'il est possible d'exprimer un même point dans des systèmes de coordonnées différents.
Nous pouvons donc envisager deux méthodes identiques en OpenGL pour faire tourner un point autour de l'origine :

Coordonnées polaires

Coordonnées cartésiennes*

glRotated(theta,0,0,1);
glBegin(GL_POINTS);
glVertex2i(r,0);
glEnd();

#include <math.h>
x=r*cos(theta*M_PI/180);
y=r*sin(theta*M_PI/180);
glBegin(GL_POINTS);
glVertex2i(x,y);
glEnd();

* les angles en OpenGL sont exprimés en degrés, cependant cos et sin, les fonctions définies dans <math.h>, attendent des radians. Il faut donc les convertir en multipliant par M_PI/180.

Coordonnées sphériques

Ce système de coordonnées peut être vu comme une généralisation des coordonnées polaires en 3D. Il est très utile et nous servira notamment à contrôler l'orientation de notre caméra à l'aide de la souris.
En coordonnées sphériques, un point est représenté par un rayon r, et deux angles thêta et phi :

Pour passer des coordonnées sphériques aux coordonnées cartésiennes qui nous sont si chères, il faut y aller en deux étapes et se ramener à des cas 2D très simples.

Tout d'abord considérons seulement le rayon r et l'angle phi. Nous nous ramenons à un cas de coordonnées polaires sur le plan XZ :

Nous pouvons en déduire directement la coordonnée Z de notre point avec le sinus, et le cosinus nous permet de définir une variable intermédiaire rtemp.

rtemp est le rayon d'un cercle intermédiaire placé à l'altitude Z de notre point :

En regardant notre sphère du dessus et considérant le cercle de rayon rtemp avec le dernier angle thêta, nous sommes à nouveau dans un simple problème de coordonnées polaires. Il est donc aisé de déterminer les coordonnées X et Y finales de notre point :

En résumé nous avons donc tout simplement :

Comme je l'ai dit plus haut, ce système de coordonnées nous sera très utile car nous pourrons ainsi contrôler l'orientation de la caméra à la souris : l'angle thêta contrôle l'orientation horizontale du regard (comme quand on fait « non » de la tête) et l'angle phi contrôle l'orientation verticale du regard (comme quand on fait « oui » de la tête).

Ce chapitre, d'un niveau mathématique assez facile (collège), viens trouver tout son sens dans le chapitre : le contrôle avancé de la caméra, où nous contrôlons une caméra de type free-fly avec la souris et le clavier.

Le cercle trigonométrique Les matrices

Les matrices

Systèmes de coordonnées L'outil matrice

Cette annexe mathématique est un passage obligé pour tous ceux qui veulent comprendre ce qui se passe réellement derrière les calculs qu'OpenGL fait pour nous.
Bien que l' objet mathématique matrice ne soit enseigné à l'école que dans le supérieur il n'a rien de mystique et une utilisation basique avec peu de connaissances en est tout à fait possible.
Je ne détaillerai ici que ce que nous avons besoin de savoir pour le cours d'OpenGL et vous verrez que ça n'a vraiment rien de sorcier.
Les novices y découvriront donc un nouvel objet mathématique très pratique, ceux qui les connaissent déjà dans d'autres contextes (trouver le Kernel, le vecteur propre, oui oui je suis passé par là aussi ;) ) quant à eux vont enfin voir une application pratique et très simple des matrices.

L'outil matrice

Les matrices Transformations

L'outil matrice

Représentation

Une matrice ( matrix en anglais) se représente comme un tableau de nombres composé de lignes et de colonnes.

Un exemple de matrice

On désigne généralement un élément par son numéro de ligne suivi de son numéro de colonne. Par exemple pour la matrice ci-dessus, l'élément en (1,2) est 15.

Les matrices peuvent être de toutes tailles mais dans le cas d'OpenGL les matrices que nous utiliserons, implicitement ou non, seront des matrices 4x4 (4 lignes, 4 colonnes). Cette taille n'est pas anodine et est liée à l'utilisation géométrique qui en est faite en 3D (nous le verrons juste après).

Tout comme avec les nombres ou les vecteurs, nous pouvons effectuer certaines opérations sur les matrices. La seule qui nous intéresse ici est la multiplication.

Multiplication de matrices

Multiplier deux matrices est une gymnastique facile et rigolote. Mais les erreurs quand on le fait à la main sont assez fréquentes (tous ces chiffres qui se croisent, pauvres de nous ! ^^ ).

Le principe est simple, pour calculer l'élément (i,j) de la matrice C, produit de deux matrices A et B, on multiplie la ligne i de A par la colonne j de B comme formalisé par :

Cette formule un peu barbare se résume très simplement par le schéma suivant :

Principe de la multiplication matrice x matrice

Et pour bien comprendre ce que l'on fait de chaque élément, voici un calcul étape par étape pour 2 matrices simples :

Détail d'une multiplication matrice x matrice

Comme vous le voyez pour multiplier une ligne par une colonne, on fait la somme de chaque sous-produit entre éléments de la ligne de la matrice de gauche et de la colonne de la matrice de droite. La meilleure façon pour bien assimiler la technique est la pratique avec un papier et un crayon (ça va pour les matrices simples seulement, quand ça devient grand on s'embrouille vite :lol: ).

Pour une raison évidente, comme nous multiplions des lignes par des colonnes, on ne peut multiplier deux matrices A et Bque si le nombre de colonnes de A est égal au nombre de lignes de B. Mais rassurez-vous, vous n'avez pas à vous en préoccuper car dans notre cas nous utilisons des matrices carrées (autant de lignes que de colonnes) et donc cette condition est toujours vérifiée.

Contrairement aux nombres, la multiplication entre matrices n'est pas commutative ( AxB != BxA ). L'ordre des multiplications est donc important (comme nous le verrons lors de la combinaison de transformations ).

Multiplication matrice x vecteur

Un vecteur n'est qu'un cas particulier de matrice avec une seule colonne. Il est donc tout à fait possible de multiplier une matrice par un vecteur (si la condition sur les tailles énoncée plus haut est remplie).

Exemple :

Multiplication matrice x vecteur (le résultat est un vecteur)

Matrice identité

La matrice identité est une matrice particulière, souvent notée I, dont tous les éléments de la diagonale sont à 1 (exemple en 4x4) :

C'est ce que l'on appelle un élément neutre, en effet un vecteur multiplié par la matrice identité en ressort inchangé :

Inverse

La dernière propriété qui nous intéresse sur les matrices est la notion d'inverse. Si par exemple nous avons une matrice M qui vient coder d'une certaine façon une translation de (1,1,1), alors son inverse M' sera une translation de (-1,-1,-1). Donc si l'on applique la matrice et son inverse à la suite à un vecteur, il reviendra donc comme il était au début. Mathématiquement on écrit ça comme ceci :

Le produit d'une matrice avec son inverse donne l'identité. L'exemple qui était pris (translation) était très simple. Dans le cas général calculer l'inverse d'une matrice est assez barbare mais je vous en donnerai l'implémentation quand nous en aurons besoin.

Les matrices Transformations

Transformations

L'outil matrice Combinaison de transformations

Transformations

Maintenant que nous comprenons l'outil mathématique matrice, je vais vous en montrer une utilisation bien pratique : les transformations géométriques.

Nous l'avons vu plus haut, multiplier une matrice par un vecteur donne un autre vecteur. Ce vecteur résultat n'est autre que la transformée du vecteur initial par la transformation « contenue » dans la matrice.

On se sert donc d'une simple multiplication matrice de transformation x vecteur de coordonnées pour obtenir les coordonnées transformées d'un point :

Comme il est possible en utilisant une matrice de transformer un vecteur par un autre, nous allons utiliser cette propriété pour stocker dans la matrice des éléments pour exécuter chaque transformation élémentaire dont nous avons besoin en 3D : la rotation, la translation, et le changement d'échelle :

Vous trouverez l'explication numérique de chaque transformation un peu après.

À quoi servent donc les éléments du bas qui n'ont d'utilité pour aucune transformation apparemment ?

Le fait que la matrice fasse 4 colonnes de large est fixé pour nos besoins (rotation, scale, translation). Mais pour pouvoir faciliter les choses et plus tard savoir, à partir d'une matrice, trouver son inverse, il faut que celle-ci soit carrée (autant de lignes que de colonnes). C'est pourquoi la matrice de transformation possède aussi 4 lignes au lieu de 3. Il s'avère qu'une partie de la 4e ligne est utilisée par OpenGL pour la projection mais nous n'avons pas besoin d'en savoir plus sur le sujet.

Coordonnées homogènes

Ok pour la taille de la matrice mais nos vecteurs sont en 3D hein, c'est quoi ce 4e élément ? C'est le temps ?

Non non, ici nous ne travaillons pas en 4D :) mais bien en 3D. Vous le savez maintenant pour pouvoir multiplier une matrice et un vecteur il faut une condition particulière sur les dimensions : la taille du vecteur doit être égale au nombre de colonnes de la matrice, ici 4. Nous introduisons donc temporairement une 4e coordonnée (appelée w) pour exprimer et former ce que l'on appelle une coordonnée homogène.

Pour passer d'un vecteur 3D (x,y,z) à son équivalent en coordonnées homogènes il suffit de rajouter un 1 soit (x,y,z,1). Par contre pour passer d'un vecteur en coordonnées homogènes (x,y,z,w) à sa version 3D normale il faut diviser les 3 premières coordonnées par la dernière soit : (x/w,y/w,z/w).
Dans notre cas (les transformations), la 4e coordonnée n'est jamais modifiée et reste donc toujours à 1. Nous n'avons donc pas à nous en soucier.

Voyons donc maintenant ce qui est mis réellement dans la matrice pour coder les transformations.

Identité

Nous l'avons vu plus tôt cette matrice ne fait rien. Ça peut paraître inutile mais c'est ce que nous utiliserons pour réinitialiser la matrice à un état connu dont nous sommes sûrs qu'il n'affecte par les coordonnées des vertices.

Translation

Une translation de (x,y,z) s'écrit matriciellement :

Et en effet si on effectue la multiplication d'un vecteur quelconque (a,b,c) par cette matrice de translation, on obtient un vecteur résultat qui n'est ni plus ni moins que l'image du premier vecteur par cette translation.

Changement d'échelle (Scale)

Un changement d'échelle s'écrit matriciellement :

En effectuant la multiplication de la matrice de changement d'échelle par un vecteur quelconque on voit bien que ses composantes sont multipliées par chacune des composantes du changement d'échelle.

Rotation

Attention accrochez-vous, une rotation d'angle thêta, autour d'un axe quelconque (x,y,z) s'écrit matriciellement :

Ça fait peur hein ! :diable: Ici c'est le cas le plus général qui soit. Simplifions vite cela en prenant le cas particulier de la rotation d'angle thêta (et oui toujours) autour de l'axe Z (0,0,1). Tout ce qui concerne x et y s'en va et on se retrouve avec une matrice qui fait nettement moins peur :

Pour vérifier que cela marche bien comme on l'entend, prenons encore un cas plus particulier : celui de la rotation de 90° autour de l'axe Z et appliquons-la au vecteur (1,1) :

Par le calcul on obtient donc le vecteur (-1,1) en rouge qui est en effet la rotation de 90° du vecteur (1,1) en noir.

L'outil matrice Combinaison de transformations

Combinaison de transformations

Transformations Créer une vidéo de votre programme

Combinaison de transformations

Pour l'instant nous avons vu comment écrire chaque transformation de base matriciellement. Mais qu'en est-il quand nous souhaitons en faire plusieurs à la suite ?

Eh bien il suffit de les multiplier ! On fait ce qu'on appelle des multiplications à droite . Si nous voulons d'abord faire une translation puis une rotation, la matrice représentant la transformation totale sera : Translation x Rotation.

Exemple :
Translation de (1,0,0) suivie d'une rotation de 90° autour de l'axe Z :

Ordre des transformations

L'ordre dans lequel sont faites les transformations est important. En effet faire une translation suivie d'une rotation n'a pas le même effet que faire une rotation suivie d'une translation.

Reprenons l'exemple de la translation de (1,0,0) mais cette fois-ci précédée d'une rotation de 90° autour de l'axe Z :

La matrice finale n'est pas la même que précédemment. Et en effet si on compare graphiquement, l'effet est totalement différent :

Importance de l'ordre des transformations

Ce chapitre assez mathématique peut nécessiter plusieurs lectures et je vous conseille de le relire conjointement avec le chapitre sur les transformations pour avoir des exemples appliqués à OpenGL.

Si vous voulez en savoir un peu plus sur l'outil matrice je vous conseille d'aller faire un tour sur Wikipédia.

Et enfin si vous vous demandez d'où je tire toutes ces informations barbares sur les représentations matricielles des transformations, je vous conseille, si ce n'est déjà fait, de jeter un coup d'oeil dans l'excellente documentation d'OpenGL, notamment les pages de :

  • glLoadIdentity ;

  • glTranslate ;

  • glRotate ;

  • glScale ;

  • glMultMatrix (pour la partie sur la combinaison de transformations).

Toutes ces informations combinées nous permettent de mieux comprendre comment fonctionne OpenGL et surtout d'être capables d'implémenter nous-mêmes les transformations (nous serons amenés à les utiliser).

Transformations Créer une vidéo de votre programme

Créer une vidéo de votre programme

Combinaison de transformations Enregistrer la vidéo

Vous avez peut-être été surpris de voir apparaître dans mon tutoriel des vidéos montrant les résultats animés des chapitres.
Cette annexe a pour but de vous expliquer comment procéder pour créer des vidéos montrant vos jeux/animations 3D sous Windows.

Si vous n'avez pas Windows, n'ayez crainte ! Vos programmes OpenGL sont censés être portables si vous suivez correctement le tutoriel jusqu'à présent. Il vous suffit d'utiliser l'ordi d'un ami (on a tous un ami sous Windows :p ) en lui emmenant vos sources pour les compiler sous Windows avec Code::Blocks par exemple.

Enregistrer la vidéo

Créer une vidéo de votre programme Encoder la vidéo

Enregistrer la vidéo

La première étape est donc l'enregistrement de la vidéo. Pour ce faire nous allons utiliser un logiciel bien pratique : Fraps.

Télécharger Fraps

Ce logiciel est disponible gratuitement à l'adresse suivante : http://www.fraps.com/download.php

La version gratuite possède cependant certaines limites :

  • les vidéos seront limitées à 30 secondes ;

  • un petit texte « www.fraps.com » apparaîtra en haut des vidéos.

Le texte n'est pas trop handicapant, car discret et rien ne vous empêche de faire un montage de plusieurs vidéos de 30 secondes (une vidéo de démo d'un jeu ne s'attarde jamais bien longtemps sur un même endroit).

Configurer Fraps

Après avoir installé et lancé Fraps, rendez-vous dans l'onglet « Movies » pour configurer les options de capture de vidéo :

  1. Le répertoire où seront stockées les vidéos.

    Pour des raisons d'optimisation de performances, les vidéos en sortie de Fraps seront très peu compressées. Pensez donc à spécifier un répertoire sur un disque où il y a de la place.

  2. Utilisez de préférence Full-Size et changez la taille de votre fenêtre dans votre code. Cela évitera à Fraps d'avoir à redimensionner les images à la volée. En full Size avec une fenêtre de 640x480, la vidéo sera de taille 640x480.

  3. Choisissez le nombre d'images par seconde désirées pour votre vidéo. 30 est un très bon compromis entre fluidité et taille de la vidéo.

    Pour respecter le framerate choisi, Fraps viendra limiter lui-même les FPS de votre application. Je vous conseille donc fortement de désactiver la limitation des FPS dans votre code avant d'enregistrer votre vidéo (il suffit simplement de commenter l'appel à SDL_Delay).

  4. Choisissez une touche qui déclenchera, une fois votre application lancée, l'enregistrement de la vidéo. Vous pouvez garder la touche par défaut, ou changer si cette touche est déjà utilisée dans votre programme.

  5. Enfin minimisez Fraps (ça ne sert à rien de laisser la fenêtre visible) dans le systray.

Enregistrement de la vidéo

En lançant votre application avec Fraps actif, vous verrez apparaître un nombre sur votre fenêtre. C'est le nombre d'images par seconde. En effet Fraps sert à faire des vidéos mais peut aussi vous aider à tester les performances de votre application.

Quand vous désirez commencer l'enregistrement, appuyez sur la touche choisie. Le nombre devient alors rouge pour vous signaler que Fraps a pris le contrôle du nombre d'images par seconde. Au bout de 30 secondes (pour la version gratuite), ou si vous appuyez à nouveau sur la touche, l'enregistrement s'arrête. Vous pouvez recommencer autant de fois que vous voulez sans quitter votre application tant que vous avez de la place sur votre disque dur. :p

En se rendant dans le répertoire des vidéos, on voit un nouveau fichier apparaître avec le nom de votre exécutable ainsi que la date. Ainsi il n'y pas de risque d'écrasement entre deux vidéos consécutives.

Cependant comme je vous l'ai dit plus haut, la vidéo n'est pas compressée et prend donc pas mal de place. De plus, elle n'est lisible pour l'instant que par vous (ou toute autre personne ayant installé Fraps). Il va donc falloir l'encoder dans un format courant : Xvid (alternative non propriétaire au divX).

Créer une vidéo de votre programme Encoder la vidéo

Encoder la vidéo

Enregistrer la vidéo

Encoder la vidéo

Pour encoder la vidéo nous allons utiliser un logiciel libre : VirtualDub.

Vous pouvez le télécharger à l'adresse suivante : http://virtualdub.sourceforge.net/.

Comme nous allons utiliser le format avi/Xvid, il nous faut les codecs appropriés que vous pouvez télécharger ici : XviD codec vX.X.X for Windows(by Koepi).

Même si vous utilisez comme moi le très bon lecteur VLC qui ne nécessite pas de codecs tiers, il est nécessaire de posséder les codecs XviD pour pouvoir encoder la vidéo.

Configuration de VirtualDub

Après avoir installé VirtualDub et les codecs, lancez VirtualDub et ouvrez votre vidéo. Dans le menu « Video » vérifiez que « Full processing mode » est bien sélectionné :

Toujours dans ce même menu « Video », allez dans « Compression... » et sélectionnez le codec de compression désiré : ici XviD. (Si vous ne choisissez pas de compression, la vidéo finale sera très volumineuse).

Il ne reste plus qu'à lancer l'encodage en allant dans le menu « File » > « Save as AVI... » :

VirtualDub vous affiche des informations le temps de l'encodage (vraiment rapide pour une vidéo de 30 secondes) :

Enfin, en regardant le répertoire où la vidéo a été enregistrée, on constate que la taille a énormément diminué :

Vous pouvez maintenant distribuer vos vidéos librement pour montrer vos prouesses en OpenGL !

Comme vous pouvez le voir l'utilisation combinée de deux logiciels puissants, Fraps et VirtualDub, permet très facilement d'enregistrer des vidéos de vos applications 3D.

Pour vous faire saliver un peu sur la suite du tutoriel voici justement une vidéo que j'ai réalisée avec la technique expliquée dans ce chapitre :

Téléchargez une vidéo annoncant un chapitre futur de ce tuto (7.8 Mo)

Enregistrer la vidéo


OpenGL Programming Guide
Programming Guide > About Page

The Official Guide to
Learning OpenGL, Version 1.1

About This Guide

The OpenGL graphics system is a software interface to graphics hardware. (The GL stands for Graphics Library.) It allows you to create interactive programs that produce color images of moving three-dimensional objects. With OpenGL, you can control computer-graphics technology to produce realistic pictures or ones that depart from reality in imaginative ways. This guide explains how to program with the OpenGL graphics system to deliver the visual effect you want.

What This Guide Contains

This guide has 14 chapters, one more than the ideal number. The first five chapters present basic information that you need to understand to be able to draw a properly colored and lit three-dimensional object on the screen.

  • Chapter 1, "Introduction to OpenGL,"
    provides a glimpse into the kinds of things OpenGL can do. It also presents a simple OpenGL program and explains essential programming details you need to know for subsequent chapters.
  • Chapter 2, "State Management and Drawing Geometric Objects,"
    explains how to create a three-dimensional geometric description of an object that is eventually drawn on the screen.
  • Chapter 3, "Viewing,"
  • describes how such three-dimensional models are transformed before being drawn onto a two-dimensional screen. You can control these transformations to show a particular view of a model.
  • Chapter 4, "Color,"
    describes how to specify the color and shading method used to draw an object.
  • Chapter 5, "Lighting,"
    explains how to control the lighting conditions surrounding an object and how that object responds to light (that is, how it reflects or absorbs light). Lighting is an important topic, since objects usually don't look three-dimensional until they're lit.

The remaining chapters explain how to optimize or add sophisticated features to your three-dimensional scene. You might choose not to take advantage of many of these features until you're more comfortable with OpenGL. Particularly advanced topics are noted in the text where they occur.

  • Chapter 6, "Blending, Antialiasing, Fog, and Polygon Offset,"
    describes techniques essential to creating a realistic scene - alpha blending (to create transparent objects), antialiasing (to eliminate jagged edges), atmospheric effects (to simulate fog or smog), and polygon offset (to remove visual artifacts when highlighting the edges of filled polygons).
  • Chapter 7, "Display Lists,"
    discusses how to store a series of OpenGL commands for execution at a later time. You'll want to use this feature to increase the performance of your OpenGL program.
  • Chapter 8, "Drawing Pixels, Bitmaps, Fonts, and Images,"
    discusses how to work with sets of two-dimensional data as bitmaps or images. One typical use for bitmaps is describing characters in fonts.
  • Chapter 9, "Texture Mapping,"
    explains how to map one- and two-dimensional images called textures onto three-dimensional objects. Many marvelous effects can be achieved through texture mapping.
  • Chapter 10, "The Framebuffer,"
    describes all the possible buffers that can exist in an OpenGL implementation and how you can control them. You can use the buffers for such effects as hidden-surface elimination, stenciling, masking, motion blur, and depth-of-field focusing.
  • Chapter 11, "Tessellators and Quadrics,"
    shows how to use the tessellation and quadrics routines in the GLU (OpenGL Utility Library).
  • Chapter 12, "Evaluators and NURBS,"
    gives an introduction to advanced techniques for efficiently generating curves or surfaces.
  • Chapter 13, "Selection and Feedback,"
    explains how you can use OpenGL's selection mechanism to select an object on the screen. It also explains the feedback mechanism, which allows you to collect the drawing information OpenGL produces rather than having it be used to draw on the screen.
  • Chapter 14, "Now That You Know,"
    describes how to use OpenGL in several clever and unexpected ways to produce interesting results. These techniques are drawn from years of experience with both OpenGL and the technological precursor to OpenGL, the Silicon Graphics IRIS Graphics Library.

In addition, there are several appendices that you will likely find useful.

  • Appendix A, "Order of Operations,"
    gives a technical overview of the operations OpenGL performs, briefly describing them in the order in which they occur as an application executes.
  • Appendix B, "State Variables,"
    lists the state variables that OpenGL maintains and describes how to obtain their values.
  • Appendix C, "OpenGL and Window Systems,"
    briefly describes the routines available in window-system specific libraries, which are extended to support OpenGL rendering. WIndow system interfaces to the X Window System, Apple MacIntosh, IBM OS/2, and Microsoft Windows NT and Windows 95 are discussed here.
  • Appendix D, "Basics of GLUT: The OpenGL Utility Toolkit,"
    discusses the library that handles window system operations. GLUT is portable and it makes code examples shorter and more comprehensible.
  • Appendix E, "Calculating Normal Vectors,"
    tells you how to calculate normal vectors for different types of geometric objects.
  • Appendix F, "Homogeneous Coordinates and Transformation Matrices,"
    explains some of the mathematics behind matrix transformations.
  • Appendix G, "Programming Tips,"
    lists some programming tips based on the intentions of the designers of OpenGL that you might find useful.
  • Appendix H, "OpenGL Invariance,"
    describes when and where an OpenGL implementation must generate the exact pixel values described in the OpenGL specification.
  • Appendix I, "Color Plates,"
    contains the color plates that appear in the printed version of this guide.

What's New in This Edition

To the question, "What's new in this edition?" the wiseacre answer is "About 100 pages." The more informative answer follows.

  • Detailed information about the following new features of OpenGL Version 1.1 has been added.
    • Vertex arrays
    • Texturing enhancements, including texture objects (including residency and prioritization), internal texture image format, texture subimages, texture proxies, and copying textures from frame buffer data
    • Polygon offset
    • Logical operation in RGBA mode
  • Program examples have been converted to Mark Kilgard's GLUT, which stands for Graphics Library Utility Toolkit. GLUT is an increasingly popular windowing toolkit, which is well-documented and has been ported to different window systems.
  • More detail about some topics that were in the first edition, especially coverage of the OpenGL Utility (GLU) Library.
    • An entire chapter on GLU tessellators and quadrics
    • A section (in Chapter 3) on the use of gluProject() and gluUnProject(), which mimics or reverses the operations of
      the geometric processing pipeline (This has been the subject of frequent discussions on the Internet newsgroup on OpenGL, comp.graphics.api.opengl.)
    • Expanded coverage (and more diagrams) about images
    • Changes to GLU NURBS properties
    • Error handling and vendor-specific extensions to OpenGL
    • Appendix C expanded to include OpenGL interfaces to several window/operating systems

The first edition's appendix on the OpenGL Utility Library was removed, and its information has been integrated into other chapters.
  • A much larger and more informative index
  • Bug fixes and minor topic reordering. Moving the display list chapter is the most noticeable change.

What You Should Know Before Reading This Guide

This guide assumes only that you know how to program in the C language and that you have some background in mathematics (geometry, trigonometry, linear algebra, calculus, and differential geometry). Even if you have little or no experience with computer-graphics technology, you should be able to follow most of the discussions in this book. Of course, computer graphics is a huge subject, so you may want to enrich your learning experience with supplemental reading.

  • Computer Graphics: Principles and Practice by James D. Foley, Andries van Dam, Steven K. Feiner, and John F. Hughes (Reading, MA: Addison-Wesley, 1990) - This book is an encyclopedic treatment of the subject of computer graphics. It includes a wealth of information but is probably best read after you have some experience with the subject.
  • 3D Computer Graphics: A User's Guide for Artists and Designers by Andrew S. Glassner (New York: Design Press, 1989) - This book is a nontechnical, gentle introduction to computer graphics. It focuses on the visual effects that can be achieved rather than on the techniques needed to achieve them.

Once you begin programming with OpenGL, you might want to obtain the OpenGL Reference Manual by the OpenGL Architecture Review Board (Reading, MA: Addison-Wesley Developers Press, 1996), which is designed as a companion volume to this guide. The Reference Manual provides a technical view of how OpenGL operates on data that describes a geometric object or an image to produce an image on the screen. It also contains full descriptions of each set of related OpenGL commands - the parameters used by the commands, the default values for those parameters, and what the commands accomplish. Many OpenGL implementations have this same material on-line, in the form of man pages or other help documents, and it's probably more up-to-date. There is also a http version on the World Wide Web; consult Silicon Graphics OpenGL Web Site (http://www.sgi.com/products/software/opengl/) for the latest pointer.

OpenGL is really a hardware-independent specification of a programming interface, and you use a particular implementation of it on a particular kind of hardware. This guide explains how to program with any OpenGL implementation. However, since implementations may vary slightly - in performance and in providing additional, optional features, for example - you might want to investigate whether supplementary documentation is available for the particular implementation you're using. In addition, you might have OpenGL-related utilities, toolkits, programming and debugging support, widgets, sample programs, and demos available to you with your system.

How to Obtain the Sample Code

This guide contains many sample programs to illustrate the use of particular OpenGL programming techniques. These programs make use of Mark Kilgard's OpenGL Utility Toolkit (GLUT). GLUT is documented in OpenGL Programming for the X Window System by Mark Kilgard (Reading, MA: Addison-Wesley Developers Press, 1996). The section "OpenGL-Related Libraries" in Chapter 1 and Appendix D gives more information about using GLUT. If you have access to the Internet, you can obtain the source code for both the sample programs and GLUT for free via anonymous ftp (file-transfer protocol).

For the source code examples found in this book, grab this file:

ftp://sgigate.sgi.com/pub/opengl/opengl1_1.tar.Z

The files you receive are compressed tar archives. To uncompress and extract the files, type

uncompress opengl1_1.tar
tar xf opengl1_1.tar

For Mark Kilgard's source code for an X Window System version of GLUT, you need to know what the most current version is. The filename will be glut-i.j.tar.Z, where i is the major revision number and j is the minor revision number of the most recent version. Check the directory for the right numbers, then grab this file:

ftp://sgigate.sgi.com/pub/opengl/xjournal/GLUT/glut-i.j.tar.Z

This file must also be uncompressed and extracted by using the tar command. The sample programs and GLUT library are created as subdirectories from wherever you are in the file directory structure.

Other ports of GLUT (for example, for Microsoft Windows NT) are springing up. A good place to start searching for the latest developments in GLUT and for OpenGL, in general, is Silicon Graphics' OpenGL Web Site:

http://www.sgi.com/products/software/opengl/

Many implementations of OpenGL might also include the code samples as part of the system. This source code is probably the best source for your implementation, because it might have been optimized for your system. Read your machine-specific OpenGL documentation to see where the code samples can be found.

Errata

Although this book is ideal and perfec in every conceivable way, there is a a pointer to an errata list from the Silicon Graphics OpenGL Web Site:

http://www.sgi.com/products/software/opengl/

The authors are quite certain there will be a little note there to reassure the reader of the pristeen quality of this book.

Style Conventions

These style conventions are used in this guide:

  • Bold - Command and routine names and matrices
  • Italics - Variables, arguments, parameter names, spatial dimensions, matrix components, and the first occurrence of key terms
  • Regular - Enumerated types and defined constants

Code examples are set off from the text in a monospace font, and command summaries are shaded with gray boxes.

In a command summary, braces are used to identify choices among data types. In the following example, glCommand has four possible suffixes: s, i, f, and d, which stand for the data types GLshort, GLint, GLfloat, and GLdouble. In the function prototype for glCommand, TYPE is a wildcard that represents the data type indicated by the suffix.

void glCommand{sifd}(TYPEx1, TYPEy1, TYPEx2, TYPEy2);


OpenGL Programming Guide : Table of Contents

OpenGL Programming Guide
OpenGL Programming Guide

The Official Guide to Learning OpenGL, Version 1.1

  • About This Guide

  • Chapter 1. Introduction to OpenGL

  • Chapter 2. State Management and Drawing Geometric Objects

  • Chapter 3. Viewing

  • Chapter 4. Color

  • Chapter 5. Lighting

  • Chapter 6. Blending, Antialiasing, Fog, and Polygon Offset

  • Chapter 7. Display Lists

  • Chapter 8. Drawing Pixels, Bitmaps, Fonts, and Images

  • Chapter 9. Texture Mapping

  • Chapter 10. The Framebuffer

  • Chapter 11. Tessellators and Quadrics

  • Chapter 12. Evaluators and NURBS

  • Chapter 13. Selection and Feedback

  • Chapter 14. Now That You Know

  • Appendix A. Order of Operations

  • Appendix B. State Variables

  • Appendix C. WGL: OpenGL Extension for Microsoft Windows NT and Windows 95

  • Appendix D. Basics of GLUT: The OpenGL Utility Toolkit

  • Appendix E. Calculating Normal Vectors

  • Appendix F. Homogeneous Coordinates and Transformation Matrices

  • Appendix G. Programming Tips

  • Appendix H. OpenGL Invariance

  • Appendix I. Color Plates


Chapter 1 - OpenGL Programming Guide

OpenGL Programming Guide
Programming Guide > Chapter 1

Chapter 1
Introduction to OpenGL

Chapter Objectives

After reading this chapter, you'll be able to do the following:

  • Appreciate in general terms what OpenGL does
  • Identify different levels of rendering complexity
  • Understand the basic structure of an OpenGL program
  • Recognize OpenGL command syntax
  • Identify the sequence of operations of the OpenGL rendering pipeline
  • Understand in general terms how to animate graphics in an OpenGL program

This chapter introduces OpenGL. It has the following major sections:

  • "What Is OpenGL?" explains what OpenGL is, what it does and doesn't do, and how it works.
  • "A Smidgen of OpenGL Code" presents a small OpenGL program and briefly discusses it. This section also defines a few basic computer-graphics terms.
  • "OpenGL Command Syntax" explains some of the conventions and notations used by OpenGL commands.
  • "OpenGL as a State Machine" describes the use of state variables in OpenGL and the commands for querying, enabling, and disabling states.
  • "OpenGL Rendering Pipeline" shows a typical sequence of operations for processing geometric and image data.
  • "OpenGL-Related Libraries" describes sets of OpenGL-related routines, including an auxiliary library specifically written for this book to simplify programming examples.
  • "Animation" explains in general terms how to create pictures on the screen that move.

What Is OpenGL?

OpenGL is a software interface to graphics hardware. This interface consists of about 150 distinct commands that you use to specify the objects and operations needed to produce interactive three-dimensional applications.

OpenGL is designed as a streamlined, hardware-independent interface to be implemented on many different hardware platforms. To achieve these qualities, no commands for performing windowing tasks or obtaining user input are included in OpenGL; instead, you must work through whatever windowing system controls the particular hardware you're using. Similarly, OpenGL doesn't provide high-level commands for describing models of three-dimensional objects. Such commands might allow you to specify relatively complicated shapes such as automobiles, parts of the body, airplanes, or molecules. With OpenGL, you must build up your desired model from a small set of geometric primitives - points, lines, and polygons.

A sophisticated library that provides these features could certainly be built on top of OpenGL. The OpenGL Utility Library (GLU) provides many of the modeling features, such as quadric surfaces and NURBS curves and surfaces. GLU is a standard part of every OpenGL implementation. Also, there is a higher-level, object-oriented toolkit, Open Inventor, which is built atop OpenGL, and is available separately for many implementations of OpenGL. (See "OpenGL-Related Libraries" for more information about Open Inventor.)

Now that you know what OpenGL doesn't do, here's what it does do. Take a look at the color plates - they illustrate typical uses of OpenGL. They show the scene on the cover of this book, rendered (which is to say, drawn) by a computer using OpenGL in successively more complicated ways. The following list describes in general terms how these pictures were made.

  • "Plate 1" shows the entire scene displayed as a wireframe model - that is, as if all the objects in the scene were made of wire. Each line of wire corresponds to an edge of a primitive (typically a polygon). For example, the surface of the table is constructed from triangular polygons that are positioned like slices of pie.

Note that you can see portions of objects that would be obscured if the objects were solid rather than wireframe. For example, you can see the entire model of the hills outside the window even though most of this model is normally hidden by the wall of the room. The globe appears to be nearly solid because it's composed of hundreds of colored blocks, and you see the wireframe lines for all the edges of all the blocks, even those forming the back side of the globe. The way the globe is constructed gives you an idea of how complex objects can be created by assembling lower-level objects.

  • "Plate 2" shows a depth-cued version of the same wireframe scene. Note that the lines farther from the eye are dimmer, just as they would be in real life, thereby giving a visual cue of depth. OpenGL uses atmospheric effects (collectively referred to as fog) to achieve depth cueing.
  • "Plate 3" shows an antialiased version of the wireframe scene. Antialiasing is a technique for reducing the jagged edges (also known as jaggies) created when approximating smooth edges using pixels - short for picture elements - which are confined to a rectangular grid. Such jaggies are usually the most visible with near-horizontal or near-vertical lines.
  • "Plate 4" shows a flat-shaded, unlit version of the scene. The objects in the scene are now shown as solid. They appear "flat" in the sense that only one color is used to render each polygon, so they don't appear smoothly rounded. There are no effects from any light sources.
  • "Plate 5" shows a lit, smooth-shaded version of the scene. Note how the scene looks much more realistic and three-dimensional when the objects are shaded to respond to the light sources in the room as if the objects were smoothly rounded.
  • "Plate 6" adds shadows and textures to the previous version of the scene. Shadows aren't an explicitly defined feature of OpenGL (there is no "shadow command"), but you can create them yourself using the techniques described in Chapter 14. Texture mapping allows you to apply a two-dimensional image onto a three-dimensional object. In this scene, the top on the table surface is the most vibrant example of texture mapping. The wood grain on the floor and table surface are all texture mapped, as well as the wallpaper and the toy top (on the table).
  • "Plate 7" shows a motion-blurred object in the scene. The sphinx (or dog, depending on your Rorschach tendencies) appears to be captured moving forward, leaving a blurred trace of its path of motion.
  • "Plate 8" shows the scene as it's drawn for the cover of the book from a different viewpoint. This plate illustrates that the image really is a snapshot of models of three-dimensional objects.
  • "Plate 9" brings back the use of fog, which was seen in "Plate 2," to show the presence of smoke particles in the air. Note how the same effect in "Plate 2" now has a more dramatic impact in "Plate 9."
  • "Plate 10" shows the depth-of-field effect, which simulates the inability of a camera lens to maintain all objects in a photographed scene in focus. The camera focuses on a particular spot in the scene. Objects that are significantly closer or farther than that spot are somewhat blurred.

The color plates give you an idea of the kinds of things you can do with the OpenGL graphics system. The following list briefly describes the major graphics operations which OpenGL performs to render an image on the screen. (See "OpenGL Rendering Pipeline" for detailed information about this order of operations.)

Construct shapes from geometric primitives, thereby creating mathematical descriptions of objects. (OpenGL considers points, lines, polygons, images, and bitmaps to be primitives.)

Arrange the objects in three-dimensional space and select the desired vantage point for viewing the composed scene.

Calculate the color of all the objects. The color might be explicitly assigned by the application, determined from specified lighting conditions, obtained by pasting a texture onto the objects, or some combination of these three actions.

Convert the mathematical description of objects and their associated color information to pixels on the screen. This process is called rasterization.

During these stages, OpenGL might perform other operations, such as eliminating parts of objects that are hidden by other objects. In addition, after the scene is rasterized but before it's drawn on the screen, you can perform some operations on the pixel data if you want.

In some implementations (such as with the X Window System), OpenGL is designed to work even if the computer that displays the graphics you create isn't the computer that runs your graphics program. This might be the case if you work in a networked computer environment where many computers are connected to one another by a digital network. In this situation, the computer on which your program runs and issues OpenGL drawing commands is called the client, and the computer that receives those commands and performs the drawing is called the server. The format for transmitting OpenGL commands (called the protocol) from the client to the server is always the same, so OpenGL programs can work across a network even if the client and server are different kinds of computers. If an OpenGL program isn't running across a network, then there's only one computer, and it is both the client and the server.

A Smidgen of OpenGL Code

Because you can do so many things with the OpenGL graphics system, an OpenGL program can be complicated. However, the basic structure of a useful program can be simple: Its tasks are to initialize certain states that control how OpenGL renders and to specify objects to be rendered.

Before you look at some OpenGL code, let's go over a few terms. Rendering, which you've already seen used, is the process by which a computer creates images from models. These models, or objects, are constructed from geometric primitives - points, lines, and polygons - that are specified by their vertices.

The final rendered image consists of pixels drawn on the screen; a pixel is the smallest visible element the display hardware can put on the screen. Information about the pixels (for instance, what color they're supposed to be) is organized in memory into bitplanes. A bitplane is an area of memory that holds one bit of information for every pixel on the screen; the bit might indicate how red a particular pixel is supposed to be, for example. The bitplanes are themselves organized into a framebuffer, which holds all the information that the graphics display needs to control the color and intensity of all the pixels on the screen.

Now look at what an OpenGL program might look like. Example 1-1 renders a white rectangle on a black background, as shown in Figure 1-1.

Figure 1-1 : White Rectangle on a Black Background

Example 1-1 : Chunk of OpenGL Code

#include <whateverYouNeed.h>

main() {

   InitializeAWindowPlease();

   glClearColor (0.0, 0.0, 0.0, 0.0);
   glClear (GL_COLOR_BUFFER_BIT);
   glColor3f (1.0, 1.0, 1.0);
   glOrtho(0.0, 1.0, 0.0, 1.0, -1.0, 1.0);
   glBegin(GL_POLYGON);
      glVertex3f (0.25, 0.25, 0.0);
      glVertex3f (0.75, 0.25, 0.0);
      glVertex3f (0.75, 0.75, 0.0);
      glVertex3f (0.25, 0.75, 0.0);
   glEnd();
   glFlush();

   UpdateTheWindowAndCheckForEvents();
}

The first line of the main() routine initializes a window on the screen: The InitializeAWindowPlease() routine is meant as a placeholder for window system-specific routines, which are generally not OpenGL calls. The next two lines are OpenGL commands that clear the window to black: glClearColor() establishes what color the window will be cleared to, and glClear() actually clears the window. Once the clearing color is set, the window is cleared to that color whenever glClear() is called. This clearing color can be changed with another call to glClearColor(). Similarly, the glColor3f() command establishes what color to use for drawing objects - in this case, the color is white. All objects drawn after this point use this color, until it's changed with another call to set the color.

The next OpenGL command used in the program, glOrtho(), specifies the coordinate system OpenGL assumes as it draws the final image and how the image gets mapped to the screen. The next calls, which are bracketed by glBegin() and glEnd(), define the object to be drawn - in this example, a polygon with four vertices. The polygon's "corners" are defined by the glVertex3f() commands. As you might be able to guess from the arguments, which are (x, y, z) coordinates, the polygon is a rectangle on the z=0 plane.

Finally, glFlush() ensures that the drawing commands are actually executed rather than stored in a buffer awaiting additional OpenGL commands. The UpdateTheWindowAndCheckForEvents() placeholder routine manages the contents of the window and begins event processing.

Actually, this piece of OpenGL code isn't well structured. You may be asking, "What happens if I try to move or resize the window?" Or, "Do I need to reset the coordinate system each time I draw the rectangle?" Later in this chapter, you will see replacements for both InitializeAWindowPlease() and UpdateTheWindowAndCheckForEvents() that actually work but will require restructuring the code to make it efficient.

OpenGL Command Syntax

As you might have observed from the simple program in the previous section, OpenGL commands use the prefix gl and initial capital letters for each word making up the command name (recall glClearColor(), for example). Similarly, OpenGL defined constants begin with GL_, use all capital letters, and use underscores to separate words (like GL_COLOR_BUFFER_BIT).

You might also have noticed some seemingly extraneous letters appended to some command names (for example, the 3f in glColor3f() and glVertex3f()). It's true that the Color part of the command name glColor3f() is enough to define the command as one that sets the current color. However, more than one such command has been defined so that you can use different types of arguments. In particular, the 3 part of the suffix indicates that three arguments are given; another version of the Color command takes four arguments. The f part of the suffix indicates that the arguments are floating-point numbers. Having different formats allows OpenGL to accept the user's data in his or her own data format.

Some OpenGL commands accept as many as 8 different data types for their arguments. The letters used as suffixes to specify these data types for ISO C implementations of OpenGL are shown in Table 1-1, along with the corresponding OpenGL type definitions. The particular implementation of OpenGL that you're using might not follow this scheme exactly; an implementation in C++ or Ada, for example, wouldn't need to.

Table 1-1 : Command Suffixes and Argument Data Types

Suffix | Data Type | Typical Corresponding C-Language Type | OpenGL Type Definition
b | 8-bit integer | signed char | GLbyte
s | 16-bit integer | short | GLshort
i | 32-bit integer | int or long | GLint, GLsizei
f | 32-bit floating-point | float | GLfloat, GLclampf
d | 64-bit floating-point | double | GLdouble, GLclampd
ub | 8-bit unsigned integer | unsigned char | GLubyte, GLboolean
us | 16-bit unsigned integer | unsigned short | GLushort
ui | 32-bit unsigned integer | unsigned int or unsigned long | GLuint, GLenum, GLbitfield

Thus, the two commands

glVertex2i(1, 3);
glVertex2f(1.0, 3.0);

are equivalent, except that the first specifies the vertex's coordinates as 32-bit integers, and the second specifies them as single-precision floating-point numbers.

Note: Implementations of OpenGL have leeway in selecting which C data type to use to represent OpenGL data types. If you resolutely use the OpenGL defined data types throughout your application, you will avoid mismatched types when porting your code between different implementations.

Some OpenGL commands can take a final letter v, which indicates that the command takes a pointer to a vector (or array) of values rather than a series of individual arguments. Many commands have both vector and nonvector versions, but some commands accept only individual arguments and others require that at least some of the arguments be specified as a vector. The following lines show how you might use a vector and a nonvector version of the command that sets the current color:

glColor3f(1.0, 0.0, 0.0);
GLfloat color_array[] = {1.0, 0.0, 0.0};
glColor3fv(color_array);

Finally, OpenGL defines the typedef GLvoid. This is most often used for OpenGL commands that accept pointers to arrays of values.

In the rest of this guide (except in actual code examples), OpenGL commands are referred to by their base names only, and an asterisk is included to indicate that there may be more to the command name. For example, glColor*() stands for all variations of the command you use to set the current color. If we want to make a specific point about one version of a particular command, we include the suffix necessary to define that version. For example, glVertex*v() refers to all the vector versions of the command you use to specify vertices.

OpenGL as a State Machine

OpenGL is a state machine. You put it into various states (or modes) that then remain in effect until you change them. As you've already seen, the current color is a state variable. You can set the current color to white, red, or any other color, and thereafter every object is drawn with that color until you set the current color to something else. The current color is only one of many state variables that OpenGL maintains. Others control such things as the current viewing and projection transformations, line and polygon stipple patterns, polygon drawing modes, pixel-packing conventions, positions and characteristics of lights, and material properties of the objects being drawn. Many state variables refer to modes that are enabled or disabled with the command glEnable() or glDisable().

Each state variable or mode has a default value, and at any point you can query the system for each variable's current value. Typically, you use one of the six following commands to do this: glGetBooleanv(), glGetDoublev(), glGetFloatv(), glGetIntegerv(), glGetPointerv(), or glIsEnabled(). Which of these commands you select depends on what data type you want the answer to be given in. Some state variables have a more specific query command (such as glGetLight*(), glGetError(), or glGetPolygonStipple()). In addition, you can save a collection of state variables on an attribute stack with glPushAttrib() or glPushClientAttrib(), temporarily modify them, and later restore the values with glPopAttrib() or glPopClientAttrib(). For temporary state changes, you should use these commands rather than any of the query commands, since they're likely to be more efficient.

See Appendix B for the complete list of state variables you can query. For each variable, the appendix also lists a suggested glGet*() command that returns the variable's value, the attribute class to which it belongs, and the variable's default value.

OpenGL Rendering Pipeline

Most implementations of OpenGL have a similar order of operations, a series of processing stages called the OpenGL rendering pipeline. This ordering, as shown in Figure 1-2, is not a strict rule of how OpenGL is implemented but provides a reliable guide for predicting what OpenGL will do.

If you are new to three-dimensional graphics, the upcoming description may seem like drinking water out of a fire hose. You can skim this now, but come back to Figure 1-2 as you go through each chapter in this book.

The following diagram shows the Henry Ford assembly line approach, which OpenGL takes to processing data. Geometric data (vertices, lines, and polygons) follow the path through the row of boxes that includes evaluators and per-vertex operations, while pixel data (pixels, images, and bitmaps) are treated differently for part of the process. Both types of data undergo the same final steps (rasterization and per-fragment operations) before the final pixel data is written into the framebuffer.

Figure 1-2 : Order of Operations

Now you'll see more detail about the key stages in the OpenGL rendering pipeline.

Display Lists

All data, whether it describes geometry or pixels, can be saved in a display list for current or later use. (The alternative to retaining data in a display list is processing the data immediately - also known as immediate mode.) When a display list is executed, the retained data is sent from the display list just as if it were sent by the application in immediate mode. (See Chapter 7 for more information about display lists.)

Evaluators

All geometric primitives are eventually described by vertices. Parametric curves and surfaces may be initially described by control points and polynomial functions called basis functions. Evaluators provide a method to derive the vertices used to represent the surface from the control points. The method is a polynomial mapping, which can produce surface normal, texture coordinates, colors, and spatial coordinate values from the control points. (See Chapter 12 to learn more about evaluators.)

Per-Vertex Operations

For vertex data, next is the "per-vertex operations" stage, which converts the vertices into primitives. Some vertex data (for example, spatial coordinates) are transformed by 4 x 4 floating-point matrices. Spatial coordinates are projected from a position in the 3D world to a position on your screen. (See Chapter 3 for details about the transformation matrices.)

If advanced features are enabled, this stage is even busier. If texturing is used, texture coordinates may be generated and transformed here. If lighting is enabled, the lighting calculations are performed using the transformed vertex, surface normal, light source position, material properties, and other lighting information to produce a color value.

Primitive Assembly

Clipping, a major part of primitive assembly, is the elimination of portions of geometry which fall outside a half-space, defined by a plane. Point clipping simply passes or rejects vertices; line or polygon clipping can add additional vertices depending upon how the line or polygon is clipped.

In some cases, this is followed by perspective division, which makes distant geometric objects appear smaller than closer objects. Then viewport and depth (z coordinate) operations are applied. If culling is enabled and the primitive is a polygon, it then may be rejected by a culling test. Depending upon the polygon mode, a polygon may be drawn as points or lines. (See "Polygon Details" in Chapter 2.)

The results of this stage are complete geometric primitives, which are the transformed and clipped vertices with related color, depth, and sometimes texture-coordinate values and guidelines for the rasterization step.

Pixel Operations

While geometric data takes one path through the OpenGL rendering pipeline, pixel data takes a different route. Pixels from an array in system memory are first unpacked from one of a variety of formats into the proper number of components. Next the data is scaled, biased, and processed by a pixel map. The results are clamped and then either written into texture memory or sent to the rasterization step. (See "Imaging Pipeline" in Chapter 8.)

If pixel data is read from the frame buffer, pixel-transfer operations (scale, bias, mapping, and clamping) are performed. Then these results are packed into an appropriate format and returned to an array in system memory.

There are special pixel copy operations to copy data in the framebuffer to other parts of the framebuffer or to the texture memory. A single pass is made through the pixel transfer operations before the data is written to the texture memory or back to the framebuffer.

Texture Assembly

An OpenGL application may wish to apply texture images onto geometric objects to make them look more realistic. If several texture images are used, it's wise to put them into texture objects so that you can easily switch among them.

Some OpenGL implementations may have special resources to accelerate texture performance. There may be specialized, high-performance texture memory. If this memory is available, the texture objects may be prioritized to control the use of this limited and valuable resource. (See Chapter 9.)

Rasterization

Rasterization is the conversion of both geometric and pixel data into fragments. Each fragment square corresponds to a pixel in the framebuffer. Line and polygon stipples, line width, point size, shading model, and coverage calculations to support antialiasing are taken into consideration as vertices are connected into lines or the interior pixels are calculated for a filled polygon. Color and depth values are assigned for each fragment square.

Fragment Operations

Before values are actually stored into the framebuffer, a series of operations are performed that may alter or even throw out fragments. All these operations can be enabled or disabled.

The first operation which may be encountered is texturing, where a texel (texture element) is generated from texture memory for each fragment and applied to the fragment. Then fog calculations may be applied, followed by the scissor test, the alpha test, the stencil test, and the depth-buffer test (the depth buffer is for hidden-surface removal). Failing an enabled test may end the continued processing of a fragment's square. Then, blending, dithering, logical operation, and masking by a bitmask may be performed. (See Chapter 6 and Chapter 10) Finally, the thoroughly processedfragment is drawn into the appropriate buffer, where it has finally advanced to be a pixel and achieved its final resting place.

OpenGL-Related Libraries

OpenGL provides a powerful but primitive set of rendering commands, and all higher-level drawing must be done in terms of these commands. Also, OpenGL programs have to use the underlying mechanisms of the windowing system. A number of libraries exist to allow you to simplify your programming tasks, including the following:

  • The OpenGL Utility Library (GLU) contains several routines that use lower-level OpenGL commands to perform such tasks as setting up matrices for specific viewing orientations and projections, performing polygon tessellation, and rendering surfaces. This library is provided as part of every OpenGL implementation. Portions of the GLU are described in the OpenGL Reference Manual. The more useful GLU routines are described in this guide, where they're relevant to the topic being discussed, such as in all of Chapter 11 and in the section "The GLU NURBS Interface" in Chapter 12. GLU routines use the prefix glu.
  • For every window system, there is a library that extends the functionality of that window system to support OpenGL rendering. For machines that use the X Window System, the OpenGL Extension to the X Window System (GLX) is provided as an adjunct to OpenGL. GLX routines use the prefix glX. For Microsoft Windows, the WGL routines provide the Windows to OpenGL interface. All WGL routines use the prefix wgl. For IBM OS/2, the PGL is the Presentation Manager to OpenGL interface, and its routines use the prefix pgl.

All these window system extension libraries are described in more detail in both Appendix C. In addition, the GLX routines are also described in the OpenGL Reference Manual.
  • The OpenGL Utility Toolkit (GLUT) is a window system-independent toolkit, written by Mark Kilgard, to hide the complexities of differing window system APIs. GLUT is the subject of the next section, and it's described in more detail in Mark Kilgard's book OpenGL Programming for the X Window System (ISBN 0-201-48359-9). GLUT routines use the prefix glut. "How to Obtain the Sample Code" in the Preface describes how to obtain the source code for GLUT, using ftp.
  • Open Inventor is an object-oriented toolkit based on OpenGL which provides objects and methods for creating interactive three-dimensional graphics applications. Open Inventor, which is written in C++, provides prebuilt objects and a built-in event model for user interaction, high-level application components for creating and editing three-dimensional scenes, and the ability to print objects and exchange data in other graphics formats. Open Inventor is separate from OpenGL.

Include Files

For all OpenGL applications, you want to include the gl.h header file in every file. Almost all OpenGL applications use GLU, the aforementioned OpenGL Utility Library, which requires inclusion of the glu.h header file. So almost every OpenGL source file begins with

#include <GL/gl.h>
#include <GL/glu.h>

If you are directly accessing a window interface library to support OpenGL, such as GLX, AGL, PGL, or WGL, you must include additional header files. For example, if you are calling GLX, you may need to add these lines to your code

#include <X11/Xlib.h>
#include <GL/glx.h>

If you are using GLUT for managing your window manager tasks, you should include

#include <GL/glut.h>

Note that glut.h includes gl.h, glu.h, and glx.h automatically, so including all three files is redundant. GLUT for Microsoft Windows includes the appropriate header file to access WGL.

GLUT, the OpenGL Utility Toolkit

As you know, OpenGL contains rendering commands but is designed to be independent of any window system or operating system. Consequently, it contains no commands for opening windows or reading events from the keyboard or mouse. Unfortunately, it's impossible to write a complete graphics program without at least opening a window, and most interesting programs require a bit of user input or other services from the operating system or window system. In many cases, complete programs make the most interesting examples, so this book uses GLUT to simplify opening windows, detecting input, and so on. If you have an implementation of OpenGL and GLUT on your system, the examples in this book should run without change when linked with them.

In addition, since OpenGL drawing commands are limited to those that generate simple geometric primitives (points, lines, and polygons), GLUT includes several routines that create more complicated three-dimensional objects such as a sphere, a torus, and a teapot. This way, snapshots of program output can be interesting to look at. (Note that the OpenGL Utility Library, GLU, also has quadrics routines that create some of the same three-dimensional objects as GLUT, such as a sphere, cylinder, or cone.)

GLUT may not be satisfactory for full-featured OpenGL applications, but you may find it a useful starting point for learning OpenGL. The rest of this section briefly describes a small subset of GLUT routines so that you can follow the programming examples in the rest of this book. (See Appendix D for more details about this subset of GLUT, or see Chapters 4 and 5 of OpenGL Programming for the X Window System for information about the rest of GLUT.)

Window Management

Five routines perform tasks necessary to initialize a window.

  • glutInit(int *argc, char **argv) initializes GLUT and processes any command line arguments (for X, this would be options like -display and -geometry). glutInit() should be called before any other GLUT routine.
  • glutInitDisplayMode(unsigned int mode) specifies whether to use an RGBA or color-index color model. You can also specify whether you want a single- or double-buffered window. (If you're working in color-index mode, you'll want to load certain colors into the color map; use glutSetColor() to do this.) Finally, you can use this routine to indicate that you want the window to have an associated depth, stencil, and/or accumulation buffer. For example, if you want a window with double buffering, the RGBA color model, and a depth buffer, you might call glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH).
  • glutInitWindowPosition(int x, int y) specifies the screen location for the upper-left corner of your window.
  • glutInitWindowSize(int width, int size) specifies the size, in pixels, of your window.
  • int glutCreateWindow(char *string) creates a window with an OpenGL context. It returns a unique identifier for the new window. Be warned: Until glutMainLoop() is called (see next section), the window is not yet displayed.

The Display Callback

glutDisplayFunc(void (*func)(void)) is the first and most important event callback function you will see. Whenever GLUT determines the contents of the window need to be redisplayed, the callback function registered by glutDisplayFunc() is executed. Therefore, you should put all the routines you need to redraw the scene in the display callback function.

If your program changes the contents of the window, sometimes you will have to call glutPostRedisplay(void), which gives glutMainLoop() a nudge to call the registered display callback at its next opportunity.

Running the Program

The very last thing you must do is call glutMainLoop(void). All windows that have been created are now shown, and rendering to those windows is now effective. Event processing begins, and the registered display callback is triggered. Once this loop is entered, it is never exited!

Example 1-2 shows how you might use GLUT to create the simple program shown in Example 1-1. Note the restructuring of the code. To maximize efficiency, operations that need only be called once (setting the background color and coordinate system) are now in a procedure called init(). Operations to render (and possibly re-render) the scene are in the display() procedure, which is the registered GLUT display callback.

Example 1-2 : Simple OpenGL Program Using GLUT: hello.c

#include <GL/gl.h>
#include <GL/glut.h>

void display(void)
{
/*  clear all pixels  */
    glClear (GL_COLOR_BUFFER_BIT);

/*  draw white polygon (rectangle) with corners at
 *  (0.25, 0.25, 0.0) and (0.75, 0.75, 0.0)
 */
    glColor3f (1.0, 1.0, 1.0);
    glBegin(GL_POLYGON);
        glVertex3f (0.25, 0.25, 0.0);
        glVertex3f (0.75, 0.25, 0.0);
        glVertex3f (0.75, 0.75, 0.0);
        glVertex3f (0.25, 0.75, 0.0);
    glEnd();

/*  don't wait!
 *  start processing buffered OpenGL routines
 */
    glFlush ();
}

void init (void)
{
/*  select clearing (background) color       */
    glClearColor (0.0, 0.0, 0.0, 0.0);

/*  initialize viewing values  */
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0.0, 1.0, 0.0, 1.0, -1.0, 1.0);
}

/*
 *  Declare initial window size, position, and display mode
 *  (single buffer and RGBA).  Open window with "hello"
 *  in its title bar.  Call initialization routines.
 *  Register callback function to display graphics.
 *  Enter main loop and process events.
 */
int main(int argc, char** argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
    glutInitWindowSize (250, 250);
    glutInitWindowPosition (100, 100);
    glutCreateWindow ("hello");
    init ();
    glutDisplayFunc(display);
    glutMainLoop();
    return 0;   /* ISO C requires main to return int. */
}

Handling Input Events

You can use these routines to register callback commands that are invoked when specified events occur.

  • glutReshapeFunc(void (*func)(int w, int h)) indicates what action should be taken when the window is resized.
  • glutKeyboardFunc(void (*func)(unsigned char key, int x, int y)) and glutMouseFunc(void (*func)(int button, int state, int x, int y)) allow you to link a keyboard key or a mouse button with a routine that's invoked when the key or mouse button is pressed or released.
  • glutMotionFunc(void (*func)(int x, int y)) registers a routine to call back when the mouse is moved while a mouse button is also pressed.

Managing a Background Process

You can specify a function that's to be executed if no other events are pending - for example, when the event loop would otherwise be idle - with glutIdleFunc(void (*func)(void)). This routine takes a pointer to the function as its only argument. Pass in NULL (zero) to disable the execution of the function.

Drawing Three-Dimensional Objects

GLUT includes several routines for drawing these three-dimensional objects:

cone | icosahedron | teapot
cube | octahedron | tetrahedron
dodecahedron | sphere | torus

You can draw these objects as wireframes or as solid shaded objects with surface normals defined. For example, the routines for a cube and a sphere are as follows:

void glutWireCube(GLdouble size);

void glutSolidCube(GLdouble size);

void glutWireSphere(GLdouble radius, GLint slices, GLint stacks);

void glutSolidSphere(GLdouble radius, GLint slices, GLint stacks);

All these models are drawn centered at the origin of the world coordinate system. (See for information on the prototypes of all these drawing routines.)

Animation

One of the most exciting things you can do on a graphics computer is draw pictures that move. Whether you're an engineer trying to see all sides of a mechanical part you're designing, a pilot learning to fly an airplane using a simulation, or merely a computer-game aficionado, it's clear that animation is an important part of computer graphics.

In a movie theater, motion is achieved by taking a sequence of pictures and projecting them at 24 per second on the screen. Each frame is moved into position behind the lens, the shutter is opened, and the frame is displayed. The shutter is momentarily closed while the film is advanced to the next frame, then that frame is displayed, and so on. Although you're watching 24 different frames each second, your brain blends them all into a smooth animation. (The old Charlie Chaplin movies were shot at 16 frames per second and are noticeably jerky.) In fact, most modern projectors display each picture twice at a rate of 48 per second to reduce flickering. Computer-graphics screens typically refresh (redraw the picture) approximately 60 to 76 times per second, and some even run at about 120 refreshes per second. Clearly, 60 per second is smoother than 30, and 120 is marginally better than 60. Refresh rates faster than 120, however, are beyond the point of diminishing returns, since the human eye is only so good.

The key reason that motion picture projection works is that each frame is complete when it is displayed. Suppose you try to do computer animation of your million-frame movie with a program like this:

open_window();
for (i = 0; i < 1000000; i++) {
   clear_the_window();
   draw_frame(i);
   wait_until_a_24th_of_a_second_is_over();
}

If you add the time it takes for your system to clear the screen and to draw a typical frame, this program gives more and more disturbing results depending on how close to 1/24 second it takes to clear and draw. Suppose the drawing takes nearly a full 1/24 second. Items drawn first are visible for the full 1/24 second and present a solid image on the screen; items drawn toward the end are instantly cleared as the program starts on the next frame. They present at best a ghostlike image, since for most of the 1/24 second your eye is viewing the cleared background instead of the items that were unlucky enough to be drawn last. The problem is that this program doesn't display completely drawn frames; instead, you watch the drawing as it happens.

Most OpenGL implementations provide double-buffering - hardware or software that supplies two complete color buffers. One is displayed while the other is being drawn. When the drawing of a frame is complete, the two buffers are swapped, so the one that was being viewed is now used for drawing, and vice versa. This is like a movie projector with only two frames in a loop; while one is being projected on the screen, an artist is desperately erasing and redrawing the frame that's not visible. As long as the artist is quick enough, the viewer notices no difference between this setup and one where all the frames are already drawn and the projector is simply displaying them one after the other. With double-buffering, every frame is shown only when the drawing is complete; the viewer never sees a partially drawn frame.

A modified version of the preceding program that does display smoothly animated graphics might look like this:

open_window_in_double_buffer_mode();
for (i = 0; i < 1000000; i++) {
   clear_the_window();
   draw_frame(i);
   swap_the_buffers();
}

The Refresh That Pauses

For some OpenGL implementations, in addition to simply swapping the viewable and drawable buffers, the swap_the_buffers() routine waits until the current screen refresh period is over so that the previous buffer is completely displayed. This routine also allows the new buffer to be completely displayed, starting from the beginning. Assuming that your system refreshes the display 60 times per second, this means that the fastest frame rate you can achieve is 60 frames per second (fps), and if all your frames can be cleared and drawn in under 1/60 second, your animation will run smoothly at that rate.

What often happens on such a system is that the frame is too complicated to draw in 1/60 second, so each frame is displayed more than once. If, for example, it takes 1/45 second to draw a frame, you get 30 fps, and the graphics are idle for 1/30-1/45=1/90 second per frame, or one-third of the time.

In addition, the video refresh rate is constant, which can have some unexpected performance consequences. For example, with the 1/60 second per refresh monitor and a constant frame rate, you can run at 60 fps, 30 fps, 20 fps, 15 fps, 12 fps, and so on (60/1, 60/2, 60/3, 60/4, 60/5, ...). That means that if you're writing an application and gradually adding features (say it's a flight simulator, and you're adding ground scenery), at first each feature you add has no effect on the overall performance - you still get 60 fps. Then, all of a sudden, you add one new feature, and the system can't quite draw the whole thing in 1/60 of a second, so the animation slows from 60 fps to 30 fps because it misses the first possible buffer-swapping time. A similar thing happens when the drawing time per frame is more than 1/30 second - the animation drops from 30 to 20 fps.

If the scene's complexity is close to any of the magic times (1/60 second, 2/60 second, 3/60 second, and so on in this example), then because of random variation, some frames go slightly over the time and some slightly under. Then the frame rate is irregular, which can be visually disturbing. In this case, if you can't simplify the scene so that all the frames are fast enough, it might be better to add an intentional, tiny delay to make sure they all miss, giving a constant, slower, frame rate. If your frames have drastically different complexities, a more sophisticated approach might be necessary.

Motion = Redraw + Swap

The structure of real animation programs does not differ too much from this description. Usually, it is easier to redraw the entire buffer from scratch for each frame than to figure out which parts require redrawing. This is especially true with applications such as three-dimensional flight simulators where a tiny change in the plane's orientation changes the position of everything outside the window.

In most animations, the objects in a scene are simply redrawn with different transformations - the viewpoint of the viewer moves, or a car moves down the road a bit, or an object is rotated slightly. If significant recomputation is required for non-drawing operations, the attainable frame rate often slows down. Keep in mind, however, that the idle time after the swap_the_buffers() routine can often be used for such calculations.

OpenGL doesn't have a swap_the_buffers() command because the feature might not be available on all hardware and, in any case, it's highly dependent on the window system. For example, if you are using the X Window System and accessing it directly, you might use the following GLX routine:

void glXSwapBuffers(Display *dpy, Window window);

(See Appendix C for equivalent routines for other window systems.)

If you are using the GLUT library, you'll want to call this routine:

void glutSwapBuffers(void);

Example 1-3 illustrates the use of glutSwapBuffers() in an example that draws a spinning square as shown in Figure 1-3. The following example also shows how to use GLUT to control an input device and turn on and off an idle function. In this example, the mouse buttons toggle the spinning on and off.

Figure 1-3 : Double-Buffered Rotating Square

Example 1-3 : Double-Buffered Program: double.c

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <stdlib.h>

static GLfloat spin = 0.0;

void init(void)
{
   glClearColor (0.0, 0.0, 0.0, 0.0);
   glShadeModel (GL_FLAT);
}

void display(void)
{
   glClear(GL_COLOR_BUFFER_BIT);
   glPushMatrix();
   glRotatef(spin, 0.0, 0.0, 1.0);
   glColor3f(1.0, 1.0, 1.0);
   glRectf(-25.0, -25.0, 25.0, 25.0);
   glPopMatrix();
   glutSwapBuffers();
}

void spinDisplay(void)
{
   spin = spin + 2.0;
   if (spin > 360.0)
      spin = spin - 360.0;
   glutPostRedisplay();
}

void reshape(int w, int h)
{
   glViewport (0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   glOrtho(-50.0, 50.0, -50.0, 50.0, -1.0, 1.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
}

void mouse(int button, int state, int x, int y)
{
   switch (button) {
      case GLUT_LEFT_BUTTON:
         if (state == GLUT_DOWN)
            glutIdleFunc(spinDisplay);
         break;
      case GLUT_MIDDLE_BUTTON:
         if (state == GLUT_DOWN)
            glutIdleFunc(NULL);
         break;
      default:
         break;
   }
}

/*
 *  Request double buffer display mode.
 *  Register mouse input callback functions
 */
int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB);
   glutInitWindowSize (250, 250);
   glutInitWindowPosition (100, 100);
   glutCreateWindow (argv[0]);
   init ();
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutMouseFunc(mouse);
   glutMainLoop();
   return 0;
}


Chapter 2 - OpenGL Programming Guide

OpenGL Programming Guide
Programming Guide > Chapter 2

Chapter 2
State Management and Drawing Geometric Objects

Chapter Objectives

After reading this chapter, you'll be able to do the following:

  • Clear the window to an arbitrary color
  • Force any pending drawing to complete
  • Draw with any geometric primitive - points, lines, and polygons - in two or three dimensions
  • Turn states on and off and query state variables
  • Control the display of those primitives - for example, draw dashed lines or outlined polygons
  • Specify normal vectors at appropriate points on the surface of solid objects
  • Use vertex arrays to store and access a lot of geometric data with only a few function calls
  • Save and restore several state variables at once

Although you can draw complex and interesting pictures using OpenGL, they're all constructed from a small number of primitive graphical items. This shouldn't be too surprising - look at what Leonardo da Vinci accomplished with just pencils and paintbrushes.

At the highest level of abstraction, there are three basic drawing operations: clearing the window, drawing a geometric object, and drawing a raster object. Raster objects, which include such things as two-dimensional images, bitmaps, and character fonts, are covered in Chapter 8. In this chapter, you learn how to clear the screen and to draw geometric objects, including points, straight lines, and flat polygons.

You might think to yourself, "Wait a minute. I've seen lots of computer graphics in movies and on television, and there are plenty of beautifully shaded curved lines and surfaces. How are those drawn, if all OpenGL can draw are straight lines and flat polygons?" Even the image on the cover of this book includes a round table and objects on the table that have curved surfaces. It turns out that all the curved lines and surfaces you've seen are approximated by large numbers of little flat polygons or straight lines, in much the same way that the globe on the cover is constructed from a large set of rectangular blocks. The globe doesn't appear to have a smooth surface because the blocks are relatively large compared to the globe. Later in this chapter, we show you how to construct curved lines and surfaces from lots of small geometric primitives.

This chapter has the following major sections:

  • "A Drawing Survival Kit" explains how to clear the window and force drawing to be completed. It also gives you basic information about controlling the color of geometric objects and describing a coordinate system.
  • "Describing Points, Lines, and Polygons" shows you what the set of primitive geometric objects is and how to draw them.
  • "Basic State Management" describes how to turn on and off some states (modes) and query state variables.
  • "Displaying Points, Lines, and Polygons" explains what control you have over the details of how primitives are drawn - for example, what diameter points have, whether lines are solid or dashed, and whether polygons are outlined or filled.
  • "Normal Vectors" discusses how to specify normal vectors for geometric objects and (briefly) what these vectors are for.
  • "Vertex Arrays" shows you how to put lots of geometric data into just a few arrays and how, with only a few function calls, to render the geometry it describes. Reducing function calls may increase the efficiency and performance of rendering.
  • "Attribute Groups" reveals how to query the current value of state variables and how to save and restore several related state values all at once.
  • "Some Hints for Building Polygonal Models of Surfaces" explores the issues and techniques involved in constructing polygonal approximations to surfaces.

One thing to keep in mind as you read the rest of this chapter is that with OpenGL, unless you specify otherwise, every time you issue a drawing command, the specified object is drawn. This might seem obvious, but in some systems, you first make a list of things to draw. When your list is complete, you tell the graphics hardware to draw the items in the list. The first style is called immediate-mode graphics and is the default OpenGL style. In addition to using immediate mode, you can choose to save some commands in a list (called a display list) for later drawing. Immediate-mode graphics are typically easier to program, but display lists are often more efficient. Chapter 7 tells you how to use display lists and why you might want to use them.

A Drawing Survival Kit

This section explains how to clear the window in preparation for drawing, set the color of objects that are to be drawn, and force drawing to be completed. None of these subjects has anything to do with geometric objects in a direct way, but any program that draws geometric objects has to deal with these issues.

Clearing the Window

Drawing on a computer screen is different from drawing on paper in that the paper starts out white, and all you have to do is draw the picture. On a computer, the memory holding the picture is usually filled with the last picture you drew, so you typically need to clear it to some background color before you start to draw the new scene. The color you use for the background depends on the application. For a word processor, you might clear to white (the color of the paper) before you begin to draw the text. If you're drawing a view from a spaceship, you clear to the black of space before beginning to draw the stars, planets, and alien spaceships. Sometimes you might not need to clear the screen at all; for example, if the image is the inside of a room, the entire graphics window gets covered as you draw all the walls.

At this point, you might be wondering why we keep talking about clearing the window - why not just draw a rectangle of the appropriate color that's large enough to cover the entire window? First, a special command to clear a window can be much more efficient than a general-purpose drawing command. In addition, as you'll see in Chapter 3, OpenGL allows you to set the coordinate system, viewing position, and viewing direction arbitrarily, so it might be difficult to figure out an appropriate size and location for a window-clearing rectangle. Finally, on many machines, the graphics hardware consists of multiple buffers in addition to the buffer containing colors of the pixels that are displayed. These other buffers must be cleared from time to time, and it's convenient to have a single command that can clear any combination of them. (See Chapter 10 for a discussion of all the possible buffers.)

You must also know how the colors of pixels are stored in the graphics hardware known as bitplanes. There are two methods of storage. Either the red, green, blue, and alpha (RGBA) values of a pixel can be directly stored in the bitplanes, or a single index value that references a color lookup table is stored. RGBA color-display mode is more commonly used, so most of the examples in this book use it. (See Chapter 4 for more information about both display modes.) You can safely ignore all references to alpha values until Chapter 6.

As an example, these lines of code clear an RGBA mode window to black:

glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);

The first line sets the clearing color to black, and the next command clears the entire window to the current clearing color. The single parameter to glClear() indicates which buffers are to be cleared. In this case, the program clears only the color buffer, where the image displayed on the screen is kept. Typically, you set the clearing color once, early in your application, and then you clear the buffers as often as necessary. OpenGL keeps track of the current clearing color as a state variable rather than requiring you to specify it each time a buffer is cleared.

Chapter 4 and Chapter 10 talk about how other buffers are used. For now, all you need to know is that clearing them is simple. For example, to clear both the color buffer and the depth buffer, you would use the following sequence of commands:

glClearColor(0.0, 0.0, 0.0, 0.0);
glClearDepth(1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

In this case, the call to glClearColor() is the same as before, the glClearDepth() command specifies the value to which every pixel of the depth buffer is to be set, and the parameter to the glClear() command now consists of the bitwise OR of all the buffers to be cleared. The following summary of glClear() includes a table that lists the buffers that can be cleared, their names, and the chapter where each type of buffer is discussed.

void glClearColor(GLclampf red, GLclampf green, GLclampf blue,
GLclampf alpha);
Sets the current clearing color for use in clearing color buffers in RGBA mode. (See Chapter 4 for more information on RGBA mode.) The red, green, blue, and alpha values are clamped if necessary to the range [0,1]. The default clearing color is (0, 0, 0, 0), which is black.
void glClear(GLbitfield mask);
Clears the specified buffers to their current clearing values. The mask argument is a bitwise-ORed combination of the values listed in Table 2-1.
Table 2-1 : Clearing Buffers
Buffer | Name | Reference
Color buffer | GL_COLOR_BUFFER_BIT | Chapter 4
Depth buffer | GL_DEPTH_BUFFER_BIT | Chapter 10
Accumulation buffer | GL_ACCUM_BUFFER_BIT | Chapter 10
Stencil buffer | GL_STENCIL_BUFFER_BIT | Chapter 10

Before issuing a command to clear multiple buffers, you have to set the values to which each buffer is to be cleared if you want something other than the default RGBA color, depth value, accumulation color, and stencil index. In addition to the glClearColor() and glClearDepth() commands that set the current values for clearing the color and depth buffers, glClearIndex(), glClearAccum(), and glClearStencil() specify the color index, accumulation color, and stencil index used to clear the corresponding buffers. (See Chapter 4 and Chapter 10 for descriptions of these buffers and their uses.)

OpenGL allows you to specify multiple buffers because clearing is generally a slow operation, since every pixel in the window (possibly millions) is touched, and some graphics hardware allows sets of buffers to be cleared simultaneously. Hardware that doesn't support simultaneous clears performs them sequentially. The difference between

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

and

glClear(GL_COLOR_BUFFER_BIT);
glClear(GL_DEPTH_BUFFER_BIT);

is that although both have the same final effect, the first example might run faster on many machines. It certainly won't run more slowly.

Specifying a Color

With OpenGL, the description of the shape of an object being drawn is independent of the description of its color. Whenever a particular geometric object is drawn, it's drawn using the currently specified coloring scheme. The coloring scheme might be as simple as "draw everything in fire-engine red," or might be as complicated as "assume the object is made out of blue plastic, that there's a yellow spotlight pointed in such and such a direction, and that there's a general low-level reddish-brown light everywhere else." In general, an OpenGL programmer first sets the color or coloring scheme and then draws the objects. Until the color or coloring scheme is changed, all objects are drawn in that color or using that coloring scheme. This method helps OpenGL achieve higher drawing performance than would result if it didn't keep track of the current color.

For example, the pseudocode

set_current_color(red);
draw_object(A);
draw_object(B);
set_current_color(green);
set_current_color(blue);
draw_object(C);

draws objects A and B in red, and object C in blue. The command on the fourth line that sets the current color to green is wasted.

Coloring, lighting, and shading are all large topics with entire chapters or large sections devoted to them. To draw geometric primitives that can be seen, however, you need some basic knowledge of how to set the current color; this information is provided in the next paragraphs. (See Chapter 4 and Chapter 5 for details on these topics.)

To set a color, use the command glColor3f(). It takes three parameters, all of which are floating-point numbers between 0.0 and 1.0. The parameters are, in order, the red, green, and blue components of the color. You can think of these three values as specifying a "mix" of colors: 0.0 means don't use any of that component, and 1.0 means use all you can of that component. Thus, the code

glColor3f(1.0, 0.0, 0.0);

makes the brightest red the system can draw, with no green or blue components. All zeros makes black; in contrast, all ones makes white. Setting all three components to 0.5 yields gray (halfway between black and white). Here are eight commands and the colors they would set.

glColor3f(0.0, 0.0, 0.0);          black
glColor3f(1.0, 0.0, 0.0);           red
glColor3f(0.0, 1.0, 0.0);           green
glColor3f(1.0, 1.0, 0.0);           yellow
glColor3f(0.0, 0.0, 1.0);           blue
glColor3f(1.0, 0.0, 1.0);           magenta
glColor3f(0.0, 1.0, 1.0);           cyan
glColor3f(1.0, 1.0, 1.0);           white

You might have noticed earlier that the routine to set the clearing color, glClearColor(), takes four parameters, the first three of which match the parameters for glColor3f(). The fourth parameter is the alpha value; it's covered in detail in "Blending" in Chapter 6. For now, set the fourth parameter of glClearColor() to 0.0, which is its default value.

Forcing Completion of Drawing

As you saw in "OpenGL Rendering Pipeline" in Chapter 1, most modern graphics systems can be thought of as an assembly line. The main central processing unit (CPU) issues a drawing command. Perhaps other hardware does geometric transformations. Clipping is performed, followed by shading and/or texturing. Finally, the values are written into the bitplanes for display. In high-end architectures, each of these operations is performed by a different piece of hardware that's been designed to perform its particular task quickly. In such an architecture, there's no need for the CPU to wait for each drawing command to complete before issuing the next one. While the CPU is sending a vertex down the pipeline, the transformation hardware is working on transforming the last one sent, the one before that is being clipped, and so on. In such a system, if the CPU waited for each command to complete before issuing the next, there could be a huge performance penalty.

In addition, the application might be running on more than one machine. For example, suppose that the main program is running elsewhere (on a machine called the client) and that you're viewing the results of the drawing on your workstation or terminal (the server), which is connected by a network to the client. In that case, it might be horribly inefficient to send each command over the network one at a time, since considerable overhead is often associated with each network transmission. Usually, the client gathers a collection of commands into a single network packet before sending it. Unfortunately, the network code on the client typically has no way of knowing that the graphics program is finished drawing a frame or scene. In the worst case, it waits forever for enough additional drawing commands to fill a packet, and you never see the completed drawing.

For this reason, OpenGL provides the command glFlush(), which forces the client to send the network packet even though it might not be full. Where there is no network and all commands are truly executed immediately on the server, glFlush() might have no effect. However, if you're writing a program that you want to work properly both with and without a network, include a call to glFlush() at the end of each frame or scene. Note that glFlush() doesn't wait for the drawing to complete - it just forces the drawing to begin execution, thereby guaranteeing that all previous commands execute in finite time even if no further rendering commands are executed.

There are other situations where glFlush() is useful.

  • Software renderers that build image in system memory and don't want to constantly update the screen.
  • Implementations that gather sets of rendering commands to amortize start-up costs. The aforementioned network transmission example is one instance of this.
void glFlush(void);
Forces previously issued OpenGL commands to begin execution, thus guaranteeing that they complete in finite time.

A few commands - for example, commands that swap buffers in double-buffer mode - automatically flush pending commands onto the network before they can occur.

If glFlush() isn't sufficient for you, try glFinish(). This command flushes the network as glFlush() does and then waits for notification from the graphics hardware or network indicating that the drawing is complete in the framebuffer. You might need to use glFinish() if you want to synchronize tasks - for example, to make sure that your three-dimensional rendering is on the screen before you use Display PostScript to draw labels on top of the rendering. Another example would be to ensure that the drawing is complete before it begins to accept user input. After you issue a glFinish() command, your graphics process is blocked until it receives notification from the graphics hardware that the drawing is complete. Keep in mind that excessive use of glFinish() can reduce the performance of your application, especially if you're running over a network, because it requires round-trip communication. If glFlush() is sufficient for your needs, use it instead of glFinish().

void glFinish(void);
Forces all previously issued OpenGL commands to complete. This command doesn't return until all effects from previous commands are fully realized.

Coordinate System Survival Kit

Whenever you initially open a window or later move or resize that window, the window system will send an event to notify you. If you are using GLUT, the notification is automated; whatever routine has been registered to glutReshapeFunc() will be called. You must register a callback function that will

  • Reestablish the rectangular region that will be the new rendering canvas
  • Define the coordinate system to which objects will be drawn

In Chapter 3 you'll see how to define three-dimensional coordinate systems, but right now, just create a simple, basic two-dimensional coordinate system into which you can draw a few objects. Call glutReshapeFunc(reshape), where reshape() is the following function shown in Example 2-1.

Example 2-1 : Reshape Callback Function

void reshape (int w, int h)
{
   glViewport (0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode (GL_PROJECTION);
   glLoadIdentity ();
   gluOrtho2D (0.0, (GLdouble) w, 0.0, (GLdouble) h);
}

The internals of GLUT will pass this function two arguments: the width and height, in pixels, of the new, moved, or resized window. glViewport() adjusts the pixel rectangle for drawing to be the entire new window. The next three routines adjust the coordinate system for drawing so that the lower-left corner is (0, 0), and the upper-right corner is (w, h) (See Figure 2-1).

To explain it another way, think about a piece of graphing paper. The w and h values in reshape() represent how many columns and rows of squares are on your graph paper. Then you have to put axes on the graph paper. The gluOrtho2D() routine puts the origin, (0, 0), all the way in the lowest, leftmost square, and makes each square represent one unit. Now when you render the points, lines, and polygons in the rest of this chapter, they will appear on this paper in easily predictable squares. (For now, keep all your objects two-dimensional.)

Figure 2-1 : Coordinate System Defined by w = 50, h = 50

Describing Points, Lines, and Polygons

This section explains how to describe OpenGL geometric primitives. All geometric primitives are eventually described in terms of their vertices - coordinates that define the points themselves, the endpoints of line segments, or the corners of polygons. The next section discusses how these primitives are displayed and what control you have over their display.

What Are Points, Lines, and Polygons?

You probably have a fairly good idea of what a mathematician means by the terms point, line, and polygon. The OpenGL meanings are similar, but not quite the same.

One difference comes from the limitations of computer-based calculations. In any OpenGL implementation, floating-point calculations are of finite precision, and they have round-off errors. Consequently, the coordinates of OpenGL points, lines, and polygons suffer from the same problems.

Another more important difference arises from the limitations of a raster graphics display. On such a display, the smallest displayable unit is a pixel, and although pixels might be less than 1/100 of an inch wide, they are still much larger than the mathematician's concepts of infinitely small (for points) or infinitely thin (for lines). When OpenGL performs calculations, it assumes points are represented as vectors of floating-point numbers. However, a point is typically (but not always) drawn as a single pixel, and many different points with slightly different coordinates could be drawn by OpenGL on the same pixel.

Points

A point is represented by a set of floating-point numbers called a vertex. All internal calculations are done as if vertices are three-dimensional. Vertices specified by the user as two-dimensional (that is, with only x and y coordinates) are assigned a z coordinate equal to zero by OpenGL.

Advanced

OpenGL works in the homogeneous coordinates of three-dimensional projective geometry, so for internal calculations, all vertices are represented with four floating-point coordinates (x, y, z, w). If w is different from zero, these coordinates correspond to the Euclidean three-dimensional point (x/w, y/w, z/w). You can specify the w coordinate in OpenGL commands, but that's rarely done. If the w coordinate isn't specified, it's understood to be 1.0. (See Appendix F for more information about homogeneous coordinate systems.)

Lines

In OpenGL, the term line refers to a line segment, not the mathematician's version that extends to infinity in both directions. There are easy ways to specify a connected series of line segments, or even a closed, connected series of segments (see Figure 2-2). In all cases, though, the lines constituting the connected series are specified in terms of the vertices at their endpoints.

Figure 2-2 : Two Connected Series of Line Segments

Polygons

Polygons are the areas enclosed by single closed loops of line segments, where the line segments are specified by the vertices at their endpoints. Polygons are typically drawn with the pixels in the interior filled in, but you can also draw them as outlines or a set of points. (See "Polygon Details.")

In general, polygons can be complicated, so OpenGL makes some strong restrictions on what constitutes a primitive polygon. First, the edges of OpenGL polygons can't intersect (a mathematician would call a polygon satisfying this condition a simple polygon). Second, OpenGL polygons must be convex, meaning that they cannot have indentations. Stated precisely, a region is convex if, given any two points in the interior, the line segment joining them is also in the interior. See Figure 2-3 for some examples of valid and invalid polygons. OpenGL, however, doesn't restrict the number of line segments making up the boundary of a convex polygon. Note that polygons with holes can't be described. They are nonconvex, and they can't be drawn with a boundary made up of a single closed loop. Be aware that if you present OpenGL with a nonconvex filled polygon, it might not draw it as you expect. For instance, on most systems no more than the convex hull of the polygon would be filled. On some systems, less than the convex hull might be filled.

Figure 2-3 : Valid and Invalid Polygons

The reason for the OpenGL restrictions on valid polygon types is that it's simpler to provide fast polygon-rendering hardware for that restricted class of polygons. Simple polygons can be rendered quickly. The difficult cases are hard to detect quickly. So for maximum performance, OpenGL crosses its fingers and assumes the polygons are simple.

Many real-world surfaces consist of nonsimple polygons, nonconvex polygons, or polygons with holes. Since all such polygons can be formed from unions of simple convex polygons, some routines to build more complex objects are provided in the GLU library. These routines take complex descriptions and tessellate them, or break them down into groups of the simpler OpenGL polygons that can then be rendered. (See "Polygon Tessellation" in Chapter 11 for more information about the tessellation routines.)

Since OpenGL vertices are always three-dimensional, the points forming the boundary of a particular polygon don't necessarily lie on the same plane in space. (Of course, they do in many cases - if all the z coordinates are zero, for example, or if the polygon is a triangle.) If a polygon's vertices don't lie in the same plane, then after various rotations in space, changes in the viewpoint, and projection onto the display screen, the points might no longer form a simple convex polygon. For example, imagine a four-point quadrilateral where the points are slightly out of plane, and look at it almost edge-on. You can get a nonsimple polygon that resembles a bow tie, as shown in Figure 2-4, which isn't guaranteed to be rendered correctly. This situation isn't all that unusual if you approximate curved surfaces by quadrilaterals made of points lying on the true surface. You can always avoid the problem by using triangles, since any three points always lie on a plane.

Figure 2-4 : Nonplanar Polygon Transformed to Nonsimple Polygon

Rectangles

Since rectangles are so common in graphics applications, OpenGL provides a filled-rectangle drawing primitive, glRect*(). You can draw a rectangle as a polygon, as described in "OpenGL Geometric Drawing Primitives," but your particular implementation of OpenGL might have optimized glRect*() for rectangles.

void glRect{sifd}(TYPEx1, TYPEy1, TYPEx2, TYPEy2);
void glRect{sifd}v(TYPE*v1, TYPE*v2);
Draws the rectangle defined by the corner points (x1, y1) and (x2, y2). The rectangle lies in the plane z=0 and has sides parallel to the x- and y-axes. If the vector form of the function is used, the corners are given by two pointers to arrays, each of which contains an (x, y) pair.

Note that although the rectangle begins with a particular orientation in three-dimensional space (in the x-y plane and parallel to the axes), you can change this by applying rotations or other transformations. (See Chapter 3 for information about how to do this.)

Curves and Curved Surfaces

Any smoothly curved line or surface can be approximated - to any arbitrary degree of accuracy - by short line segments or small polygonal regions. Thus, subdividing curved lines and surfaces sufficiently and then approximating them with straight line segments or flat polygons makes them appear curved (see Figure 2-5). If you're skeptical that this really works, imagine subdividing until each line segment or polygon is so tiny that it's smaller than a pixel on the screen.

Figure 2-5 : Approximating Curves

Even though curves aren't geometric primitives, OpenGL does provide some direct support for subdividing and drawing them. (See Chapter 12 for information about how to draw curves and curved surfaces.)

Specifying Vertices

With OpenGL, all geometric objects are ultimately described as an ordered set of vertices. You use the glVertex*() command to specify a vertex.

void glVertex{234}{sifd}[v](TYPEcoords);
Specifies a vertex for use in describing a geometric object. You can supply up to four coordinates (x, y, z, w) for a particular vertex or as few as two (x, y) by selecting the appropriate version of the command. If you use a version that doesn't explicitly specify z or w, z is understood to be 0 and w is understood to be 1. Calls to glVertex*() are only effective between a glBegin() and glEnd() pair.

Example 2-2 provides some examples of using glVertex*().

Example 2-2 : Legal Uses of glVertex*()

glVertex2s(2, 3);
glVertex3d(0.0, 0.0, 3.1415926535898);
glVertex4f(2.3, 1.0, -2.2, 2.0);
GLdouble dvect[3] = {5.0, 9.0, 1992.0};
glVertex3dv(dvect);

The first example represents a vertex with three-dimensional coordinates (2, 3, 0). (Remember that if it isn't specified, the z coordinate is understood to be 0.) The coordinates in the second example are (0.0, 0.0, 3.1415926535898) (double-precision floating-point numbers). The third example represents the vertex with three-dimensional coordinates (1.15, 0.5, -1.1). (Remember that the x, y, and z coordinates are eventually divided by the w coordinate.) In the final example, dvect is a pointer to an array of three double-precision floating-point numbers.

On some machines, the vector form of glVertex*() is more efficient, since only a single parameter needs to be passed to the graphics subsystem. Special hardware might be able to send a whole series of coordinates in a single batch. If your machine is like this, it's to your advantage to arrange your data so that the vertex coordinates are packed sequentially in memory. In this case, there may be some gain in performance by using the vertex array operations of OpenGL. (See "Vertex Arrays.")

OpenGL Geometric Drawing Primitives

Now that you've seen how to specify vertices, you still need to know how to tell OpenGL to create a set of points, a line, or a polygon from those vertices. To do this, you bracket each set of vertices between a call to glBegin() and a call to glEnd(). The argument passed to glBegin() determines what sort of geometric primitive is constructed from the vertices. For example, Example 2-3> specifies the vertices for the polygon shown in Figure 2-6.

Example 2-3 : Filled Polygon

glBegin(GL_POLYGON);
   glVertex2f(0.0, 0.0);
   glVertex2f(0.0, 3.0);
   glVertex2f(4.0, 3.0);
   glVertex2f(6.0, 1.5);
   glVertex2f(4.0, 0.0);
glEnd();

Figure 2-6 : Drawing a Polygon or a Set of Points

If you had used GL_POINTS instead of GL_POLYGON, the primitive would have been simply the five points shown in Figure 2-6. Table 2-2 in the following function summary for glBegin() lists the ten possible arguments and the corresponding type of primitive.

void glBegin(GLenum mode);
Marks the beginning of a vertex-data list that describes a geometric primitive. The type of primitive is indicated by mode, which can be any of the values shown in Table 2-2.
Table 2-2 : Geometric Primitive Names and Meanings
Value | Meaning
GL_POINTS | individual points
GL_LINES | pairs of vertices interpreted as individual line segments
GL_LINE_STRIP | series of connected line segments
GL_LINE_LOOP | same as above, with a segment added between last and first vertices
GL_TRIANGLES | triples of vertices interpreted as triangles
GL_TRIANGLE_STRIP | linked strip of triangles
GL_TRIANGLE_FAN | linked fan of triangles
GL_QUADS | quadruples of vertices interpreted as four-sided polygons
GL_QUAD_STRIP | linked strip of quadrilaterals
GL_POLYGON | boundary of a simple, convex polygon

void glEnd(void);

Marks the end of a vertex-data list.

Figure 2-7 shows examples of all the geometric primitives listed in Table 2-2. The paragraphs that follow the figure describe the pixels that are drawn for each of the objects. Note that in addition to points, several types of lines and polygons are defined. Obviously, you can find many ways to draw the same primitive. The method you choose depends on your vertex data.

Figure 2-7 : Geometric Primitive Types

As you read the following descriptions, assume that n vertices (v0, v1, v2, ... , vn-1) are described between a glBegin() and glEnd() pair.

GL_POINTS | Draws a point at each of the n vertices.
GL_LINES | Draws a series of unconnected line segments. Segments are drawn between v0 and v1, between v2 and v3, and so on. If n is odd, the last segment is drawn between vn-3 and vn-2, and vn-1 is ignored.
GL_LINE_STRIP | Draws a line segment from v0 to v1, then from v1 to v2, and so on, finally drawing the segment from vn-2 to vn-1. Thus, a total of n-1 line segments are drawn. Nothing is drawn unless n is larger than 1. There are no restrictions on the vertices describing a line strip (or a line loop); the lines can intersect arbitrarily.
GL_LINE_LOOP | Same as GL_LINE_STRIP, except that a final line segment is drawn from vn-1 to v0, completing a loop.
GL_TRIANGLES | Draws a series of triangles (three-sided polygons) using vertices v0, v1, v2, then v3, v4, v5, and so on. If n isn't an exact multiple of 3, the final one or two vertices are ignored.
GL_TRIANGLE_STRIP | Draws a series of triangles (three-sided polygons) using vertices v0, v1, v2, then v2, v1, v3 (note the order), then v2, v3, v4, and so on. The ordering is to ensure that the triangles are all drawn with the same orientation so that the strip can correctly form part of a surface. Preserving the orientation is important for some operations, such as culling. (See "Reversing and Culling Polygon Faces") n must be at least 3 for anything to be drawn.
GL_TRIANGLE_FAN | Same as GL_TRIANGLE_STRIP, except that the vertices are v0, v1, v2, then v0, v2, v3, then v0, v3, v4, and so on (see Figure 2-7).
GL_QUADS | Draws a series of quadrilaterals (four-sided polygons) using vertices v0, v1, v2, v3, then v4, v5, v6, v7, and so on. If n isn't a multiple of 4, the final one, two, or three vertices are ignored.
GL_QUAD_STRIP | Draws a series of quadrilaterals (four-sided polygons) beginning with v0, v1, v3, v2, then v2, v3, v5, v4, then v4, v5, v7, v6, and so on (see Figure 2-7). n must be at least 4 before anything is drawn. If n is odd, the final vertex is ignored.
GL_POLYGON | Draws a polygon using the points v0, ... , vn-1 as vertices. n must be at least 3, or nothing is drawn. In addition, the polygon specified must not intersect itself and must be convex. If the vertices don't satisfy these conditions, the results are unpredictable.

Restrictions on Using glBegin() and glEnd()

The most important information about vertices is their coordinates, which are specified by the glVertex*() command. You can also supply additional vertex-specific data for each vertex - a color, a normal vector, texture coordinates, or any combination of these - using special commands. In addition, a few other commands are valid between a glBegin() and glEnd() pair. Table 2-3 contains a complete list of such valid commands.

Table 2-3 : Valid Commands between glBegin() and glEnd()

Command | Purpose of Command | Reference
glVertex*() | set vertex coordinates | Chapter 2
glColor*() | set current color | Chapter 4
glIndex*() | set current color index | Chapter 4
glNormal*() | set normal vector coordinates | Chapter 2
glTexCoord*() | set texture coordinates | Chapter 9
glEdgeFlag*() | control drawing of edges | Chapter 2
glMaterial*() | set material properties | Chapter 5
glArrayElement() | extract vertex array data | Chapter 2
glEvalCoord*(), glEvalPoint*() | generate coordinates | Chapter 12
glCallList(), glCallLists() | execute display list(s) | Chapter 7

No other OpenGL commands are valid between a glBegin() and glEnd() pair, and making most other OpenGL calls generates an error. Some vertex array commands, such as glEnableClientState() and glVertexPointer(), when called between glBegin() and glEnd(), have undefined behavior but do not necessarily generate an error. (Also, routines related to OpenGL, such as glX*() routines have undefined behavior between glBegin() and glEnd().) These cases should be avoided, and debugging them may be more difficult.

Note, however, that only OpenGL commands are restricted; you can certainly include other programming-language constructs (except for calls, such as the aforementioned glX*() routines). For example, Example 2-4 draws an outlined circle.

Example 2-4 : Other Constructs between glBegin() and glEnd()

#define PI 3.1415926535898
GLint circle_points = 100;
glBegin(GL_LINE_LOOP);
for (i = 0; i < circle_points; i++) {
   angle = 2*PI*i/circle_points;
   glVertex2f(cos(angle), sin(angle));
}
glEnd();

Note: This example isn't the most efficient way to draw a circle, especially if you intend to do it repeatedly. The graphics commands used are typically very fast, but this code calculates an angle and calls the sin() and cos() routines for each vertex; in addition, there's the loop overhead. (Another way to calculate the vertices of a circle is to use a GLU routine; see "Quadrics: Rendering Spheres, Cylinders, and Disks" in Chapter 11.) If you need to draw lots of circles, calculate the coordinates of the vertices once and save them in an array and create a display list (see Chapter 7), or use vertex arrays to render them.

Unless they are being compiled into a display list, all glVertex*() commands should appear between some glBegin() and glEnd() combination. (If they appear elsewhere, they don't accomplish anything.) If they appear in a display list, they are executed only if they appear between a glBegin() and a glEnd(). (See Chapter 7 for more information about display lists.)

Although many commands are allowed between glBegin() and glEnd(), vertices are generated only when a glVertex*() command is issued. At the moment glVertex*() is called, OpenGL assigns the resulting vertex the current color, texture coordinates, normal vector information, and so on. To see this, look at the following code sequence. The first point is drawn in red, and the second and third ones in blue, despite the extra color commands.

glBegin(GL_POINTS);
   glColor3f(0.0, 1.0, 0.0);                  /* green */
   glColor3f(1.0, 0.0, 0.0);                  /* red */
   glVertex(...);
   glColor3f(1.0, 1.0, 0.0);                  /* yellow */
   glColor3f(0.0, 0.0, 1.0);                  /* blue */
   glVertex(...);
   glVertex(...);
glEnd();

You can use any combination of the 24 versions of the glVertex*() command between glBegin() and glEnd(), although in real applications all the calls in any particular instance tend to be of the same form. If your vertex-data specification is consistent and repetitive (for example, glColor*, glVertex*, glColor*, glVertex*,...), you may enhance your program's performance by using vertex arrays. (See "Vertex Arrays.")

Basic State Management

In the previous section, you saw an example of a state variable, the current RGBA color, and how it can be associated with a primitive. OpenGL maintains many states and state variables. An object may be rendered with lighting, texturing, hidden surface removal, fog, or some other states affecting its appearance.

By default, most of these states are initially inactive. These states may be costly to activate; for example, turning on texture mapping will almost certainly slow down the speed of rendering a primitive. However, the quality of the image will improve and look more realistic, due to the enhanced graphics capabilities.

To turn on and off many of these states, use these two simple commands:

void glEnable(GLenum cap);
void glDisable(GLenum cap);
glEnable() turns on a capability, and glDisable() turns it off. There are over 40 enumerated values that can be passed as a parameter to glEnable() or glDisable(). Some examples of these are GL_BLEND (which controls blending RGBA values), GL_DEPTH_TEST (which controls depth comparisons and updates to the depth buffer), GL_FOG (which controls fog), GL_LINE_STIPPLE (patterned lines), GL_LIGHTING (you get the idea), and so forth.

You can also check if a state is currently enabled or disabled.

GLboolean glIsEnabled(GLenum capability)
Returns GL_TRUE or GL_FALSE, depending upon whether the queried capability is currently activated.

The states you have just seen have two settings: on and off. However, most OpenGL routines set values for more complicated state variables. For example, the routine glColor3f() sets three values, which are part of the GL_CURRENT_COLOR state. There are five querying routines used to find out what values are set for many states:

void glGetBooleanv(GLenum pname, GLboolean *params);
void glGetIntegerv(GLenum pname, GLint *params);
void glGetFloatv(GLenum pname, GLfloat *params);
void glGetDoublev(GLenum pname, GLdouble *params);
void glGetPointerv(GLenum pname, GLvoid **params);
Obtains Boolean, integer, floating-point, double-precision, or pointer state variables. The pname argument is a symbolic constant indicating the state variable to return, and params is a pointer to an array of the indicated type in which to place the returned data. See the tables in Appendix B for the possible values for pname. For example, to get the current RGBA color, a table in Appendix B suggests you use glGetIntegerv(GL_CURRENT_COLOR, params) or glGetFloatv(GL_CURRENT_COLOR, params). A type conversion is performed if necessary to return the desired variable as the requested data type.

These querying routines handle most, but not all, requests for obtaining state information. (See "The Query Commands" in Appendix B for an additional 16 querying routines.)

Displaying Points, Lines, and Polygons

By default, a point is drawn as a single pixel on the screen, a line is drawn solid and one pixel wide, and polygons are drawn solidly filled in. The following paragraphs discuss the details of how to change these default display modes.

Point Details

To control the size of a rendered point, use glPointSize() and supply the desired size in pixels as the argument.

void glPointSize(GLfloat size);
Sets the width in pixels for rendered points; size must be greater than 0.0 and by default is 1.0.

The actual collection of pixels on the screen which are drawn for various point widths depends on whether antialiasing is enabled. (Antialiasing is a technique for smoothing points and lines as they're rendered; see "Antialiasing" in Chapter 6 for more detail.) If antialiasing is disabled (the default), fractional widths are rounded to integer widths, and a screen-aligned square region of pixels is drawn. Thus, if the width is 1.0, the square is 1 pixel by 1 pixel; if the width is 2.0, the square is 2 pixels by 2 pixels, and so on.

With antialiasing enabled, a circular group of pixels is drawn, and the pixels on the boundaries are typically drawn at less than full intensity to give the edge a smoother appearance. In this mode, non-integer widths aren't rounded.

Most OpenGL implementations support very large point sizes. The maximum size for antialiased points is queryable, but the same information is not available for standard, aliased points. A particular implementation, however, might limit the size of standard, aliased points to not less than its maximum antialiased point size, rounded to the nearest integer value. You can obtain this floating-point value by using GL_POINT_SIZE_RANGE with glGetFloatv().

Line Details

With OpenGL, you can specify lines with different widths and lines that are stippled in various ways - dotted, dashed, drawn with alternating dots and dashes, and so on.

Wide Lines

void glLineWidth(GLfloat width);
Sets the width in pixels for rendered lines; width must be greater than 0.0 and by default is 1.0.

The actual rendering of lines is affected by the antialiasing mode, in the same way as for points. (See "Antialiasing" in Chapter 6.) Without antialiasing, widths of 1, 2, and 3 draw lines 1, 2, and 3 pixels wide. With antialiasing enabled, non-integer line widths are possible, and pixels on the boundaries are typically drawn at less than full intensity. As with point sizes, a particular OpenGL implementation might limit the width of nonantialiased lines to its maximum antialiased line width, rounded to the nearest integer value. You can obtain this floating-point value by using GL_LINE_WIDTH_RANGE with glGetFloatv().

Note: Keep in mind that by default lines are 1 pixel wide, so they appear wider on lower-resolution screens. For computer displays, this isn't typically an issue, but if you're using OpenGL to render to a high-resolution plotter, 1-pixel lines might be nearly invisible. To obtain resolution-independent line widths, you need to take into account the physical dimensions of pixels.

Advanced

With nonantialiased wide lines, the line width isn't measured perpendicular to the line. Instead, it's measured in the y direction if the absolute value of the slope is less than 1.0; otherwise, it's measured in the x direction. The rendering of an antialiased line is exactly equivalent to the rendering of a filled rectangle of the given width, centered on the exact line.

Stippled Lines

To make stippled (dotted or dashed) lines, you use the command glLineStipple() to define the stipple pattern, and then you enable line stippling with glEnable().

glLineStipple(1, 0x3F07);
glEnable(GL_LINE_STIPPLE);
void glLineStipple(GLint factor, GLushort pattern);
Sets the current stippling pattern for lines. The pattern argument is a 16-bit series of 0s and 1s, and it's repeated as necessary to stipple a given line. A 1 indicates that drawing occurs, and 0 that it does not, on a pixel-by-pixel basis, beginning with the low-order bit of the pattern. The pattern can be stretched out by using factor, which multiplies each subseries of consecutive 1s and 0s. Thus, if three consecutive 1s appear in the pattern, they're stretched to six if factor is 2. factor is clamped to lie between 1 and 255. Line stippling must be enabled by passing GL_LINE_STIPPLE to glEnable(); it's disabled by passing the same argument to glDisable().

With the preceding example and the pattern 0x3F07 (which translates to 0011111100000111 in binary), a line would be drawn with 3 pixels on, then 5 off, 6 on, and 2 off. (If this seems backward, remember that the low-order bit is used first.) If factor had been 2, the pattern would have been elongated: 6 pixels on, 10 off, 12 on, and 4 off. Figure 2-8 shows lines drawn with different patterns and repeat factors. If you don't enable line stippling, drawing proceeds as if pattern were 0xFFFF and factor 1. (Use glDisable() with GL_LINE_STIPPLE to disable stippling.) Note that stippling can be used in combination with wide lines to produce wide stippled lines.

Figure 2-8 : Stippled Lines

One way to think of the stippling is that as the line is being drawn, the pattern is shifted by 1 bit each time a pixel is drawn (or factor pixels are drawn, if factor isn't 1). When a series of connected line segments is drawn between a single glBegin() and glEnd(), the pattern continues to shift as one segment turns into the next. This way, a stippling pattern continues across a series of connected line segments. When glEnd() is executed, the pattern is reset, and - if more lines are drawn before stippling is disabled - the stippling restarts at the beginning of the pattern. If you're drawing lines with GL_LINES, the pattern resets for each independent line.

Example 2-5 illustrates the results of drawing with a couple of different stipple patterns and line widths. It also illustrates what happens if the lines are drawn as a series of individual segments instead of a single connected line strip. The results of running the program appear in Figure 2-9.

Figure 2-9 : Wide Stippled Lines

Example 2-5 : Line Stipple Patterns: lines.c

#include <GL/gl.h>
#include <GL/glut.h>

#define drawOneLine(x1,y1,x2,y2)  glBegin(GL_LINES);  \
   glVertex2f ((x1),(y1)); glVertex2f ((x2),(y2)); glEnd();

void init(void)
{
   glClearColor (0.0, 0.0, 0.0, 0.0);
   glShadeModel (GL_FLAT);
}

void display(void)
{
   int i;

   glClear (GL_COLOR_BUFFER_BIT);
/* select white for all lines  */
   glColor3f (1.0, 1.0, 1.0);

/* in 1st row, 3 lines, each with a different stipple  */
   glEnable (GL_LINE_STIPPLE);

   glLineStipple (1, 0x0101);  /*  dotted  */
   drawOneLine (50.0, 125.0, 150.0, 125.0);
   glLineStipple (1, 0x00FF);  /*  dashed  */
   drawOneLine (150.0, 125.0, 250.0, 125.0);
   glLineStipple (1, 0x1C47);  /*  dash/dot/dash  */
   drawOneLine (250.0, 125.0, 350.0, 125.0);
/* in 2nd row, 3 wide lines, each with different stipple */
   glLineWidth (5.0);
   glLineStipple (1, 0x0101);  /*  dotted  */
   drawOneLine (50.0, 100.0, 150.0, 100.0);
   glLineStipple (1, 0x00FF);  /*  dashed  */
   drawOneLine (150.0, 100.0, 250.0, 100.0);
   glLineStipple (1, 0x1C47);  /*  dash/dot/dash  */
   drawOneLine (250.0, 100.0, 350.0, 100.0);
   glLineWidth (1.0);

/* in 3rd row, 6 lines, with dash/dot/dash stipple  */
/* as part of a single connected line strip         */
   glLineStipple (1, 0x1C47);  /*  dash/dot/dash  */
   glBegin (GL_LINE_STRIP);
   for (i = 0; i < 7; i++)
      glVertex2f (50.0 + ((GLfloat) i * 50.0), 75.0);
   glEnd ();

/* in 4th row, 6 independent lines with same stipple  */
   for (i = 0; i < 6; i++) {
      drawOneLine (50.0 + ((GLfloat) i * 50.0), 50.0,
         50.0 + ((GLfloat)(i+1) * 50.0), 50.0);
   }

/* in 5th row, 1 line, with dash/dot/dash stipple    */
/* and a stipple repeat factor of 5                  */
   glLineStipple (5, 0x1C47);  /*  dash/dot/dash  */
   drawOneLine (50.0, 25.0, 350.0, 25.0);

   glDisable (GL_LINE_STIPPLE);
   glFlush ();
}

void reshape (int w, int h)
{
   glViewport (0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode (GL_PROJECTION);
   glLoadIdentity ();
   gluOrtho2D (0.0, (GLdouble) w, 0.0, (GLdouble) h);
}
int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
   glutInitWindowSize (400, 150);
   glutInitWindowPosition (100, 100);
   glutCreateWindow (argv[0]);
   init ();
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutMainLoop();
   return 0;
}

Polygon Details

Polygons are typically drawn by filling in all the pixels enclosed within the boundary, but you can also draw them as outlined polygons or simply as points at the vertices. A filled polygon might be solidly filled or stippled with a certain pattern. Although the exact details are omitted here, filled polygons are drawn in such a way that if adjacent polygons share an edge or vertex, the pixels making up the edge or vertex are drawn exactly once - they're included in only one of the polygons. This is done so that partially transparent polygons don't have their edges drawn twice, which would make those edges appear darker (or brighter, depending on what color you're drawing with). Note that it might result in narrow polygons having no filled pixels in one or more rows or columns of pixels. Antialiasing polygons is more complicated than for points and lines. (See "Antialiasing" in Chapter 6 for details.)

Polygons as Points, Outlines, or Solids

A polygon has two sides - front and back - and might be rendered differently depending on which side is facing the viewer. This allows you to have cutaway views of solid objects in which there is an obvious distinction between the parts that are inside and those that are outside. By default, both front and back faces are drawn in the same way. To change this, or to draw only outlines or vertices, use glPolygonMode().

void glPolygonMode(GLenum face, GLenum mode);
Controls the drawing mode for a polygon's front and back faces. The parameter face can be GL_FRONT_AND_BACK, GL_FRONT, or GL_BACK; mode can be GL_POINT, GL_LINE, or GL_FILL to indicate whether the polygon should be drawn as points, outlined, or filled. By default, both the front and back faces are drawn filled.

For example, you can have the front faces filled and the back faces outlined with two calls to this routine:

glPolygonMode(GL_FRONT, GL_FILL);
glPolygonMode(GL_BACK, GL_LINE);

Reversing and Culling Polygon Faces

By convention, polygons whose vertices appear in counterclockwise order on the screen are called front-facing. You can construct the surface of any "reasonable" solid - a mathematician would call such a surface an orientable manifold (spheres, donuts, and teapots are orientable; Klein bottles and Mobius strips aren't) - from polygons of consistent orientation. In other words, you can use all clockwise polygons, or all counterclockwise polygons. (This is essentially the mathematical definition of orientable.)

Suppose you've consistently described a model of an orientable surface but that you happen to have the clockwise orientation on the outside. You can swap what OpenGL considers the back face by using the function glFrontFace(), supplying the desired orientation for front-facing polygons.

void glFrontFace(GLenum mode);
Controls how front-facing polygons are determined. By default, mode is GL_CCW, which corresponds to a counterclockwise orientation of the ordered vertices of a projected polygon in window coordinates. If mode is GL_CW, faces with a clockwise orientation are considered front-facing.

In a completely enclosed surface constructed from opaque polygons with a consistent orientation, none of the back-facing polygons are ever visible - they're always obscured by the front-facing polygons. If you are outside this surface, you might enable culling to discard polygons that OpenGL determines are back-facing. Similarly, if you are inside the object, only back-facing polygons are visible. To instruct OpenGL to discard front- or back-facing polygons, use the command glCullFace() and enable culling with glEnable().

void glCullFace(GLenum mode);
Indicates which polygons should be discarded (culled) before they're converted to screen coordinates. The mode is either GL_FRONT, GL_BACK, or GL_FRONT_AND_BACK to indicate front-facing, back-facing, or all polygons. To take effect, culling must be enabled using glEnable() with GL_CULL_FACE; it can be disabled with glDisable() and the same argument.

Advanced

In more technical terms, the decision of whether a face of a polygon is front- or back-facing depends on the sign of the polygon's area computed in window coordinates. One way to compute this area is

where xi and yi are the x and y window coordinates of the ith vertex of the n-vertex polygon and

Assuming that GL_CCW has been specified, if a>0, the polygon corresponding to that vertex is considered to be front-facing; otherwise, it's back-facing. If GL_CW is specified and if a<0, then the corresponding polygon is front-facing; otherwise, it's back-facing.

Try This

Modify Example 2-5 by adding some filled polygons. Experiment with different colors. Try different polygon modes. Also enable culling to see its effect.

Stippling Polygons

By default, filled polygons are drawn with a solid pattern. They can also be filled with a 32-bit by 32-bit window-aligned stipple pattern, which you specify with glPolygonStipple().

void glPolygonStipple(const GLubyte *mask);
Defines the current stipple pattern for filled polygons. The argument mask is a pointer to a 32 ´ 32 bitmap that's interpreted as a mask of 0s and 1s. Where a 1 appears, the corresponding pixel in the polygon is drawn, and where a 0 appears, nothing is drawn. Figure 2-10 shows how a stipple pattern is constructed from the characters in mask. Polygon stippling is enabled and disabled by using glEnable() and glDisable() with GL_POLYGON_STIPPLE as the argument. The interpretation of the mask data is affected by the glPixelStore*() GL_UNPACK* modes. (See "Controlling Pixel-Storage Modes" in Chapter 8 .)

In addition to defining the current polygon stippling pattern, you must enable stippling:

glEnable(GL_POLYGON_STIPPLE);

Use glDisable() with the same argument to disable polygon stippling.

Figure 2-11 shows the results of polygons drawn unstippled and then with two different stippling patterns. The program is shown in Example 2-6. The reversal of white to black (from Figure 2-10 to Figure 2-11) occurs because the program draws in white over a black background, using the pattern in Figure 2-10 as a stencil.

Figure 2-10 : Constructing a Polygon Stipple Pattern

Figure 2-11 : Stippled Polygons

Example 2-6 : Polygon Stipple Patterns: polys.c

#include <GL/gl.h>
#include <GL/glut.h>
void display(void)
{
   GLubyte fly[] = {
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x03, 0x80, 0x01, 0xC0, 0x06, 0xC0, 0x03, 0x60,
      0x04, 0x60, 0x06, 0x20, 0x04, 0x30, 0x0C, 0x20,
      0x04, 0x18, 0x18, 0x20, 0x04, 0x0C, 0x30, 0x20,
      0x04, 0x06, 0x60, 0x20, 0x44, 0x03, 0xC0, 0x22,
      0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22,
      0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22,
      0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22,
      0x66, 0x01, 0x80, 0x66, 0x33, 0x01, 0x80, 0xCC,
      0x19, 0x81, 0x81, 0x98, 0x0C, 0xC1, 0x83, 0x30,
      0x07, 0xe1, 0x87, 0xe0, 0x03, 0x3f, 0xfc, 0xc0,
      0x03, 0x31, 0x8c, 0xc0, 0x03, 0x33, 0xcc, 0xc0,
      0x06, 0x64, 0x26, 0x60, 0x0c, 0xcc, 0x33, 0x30,
      0x18, 0xcc, 0x33, 0x18, 0x10, 0xc4, 0x23, 0x08,
      0x10, 0x63, 0xC6, 0x08, 0x10, 0x30, 0x0c, 0x08,
      0x10, 0x18, 0x18, 0x08, 0x10, 0x00, 0x00, 0x08};
   GLubyte halftone[] = {
      0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
      0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
      0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
      0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
      0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
      0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
      0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
      0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
      0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
      0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
      0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
      0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
      0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
      0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
      0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
      0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55};

   glClear (GL_COLOR_BUFFER_BIT);
   glColor3f (1.0, 1.0, 1.0);

/*  draw one solid, unstippled rectangle,       */
/*  then two stippled rectangles                */
   glRectf (25.0, 25.0, 125.0, 125.0);
   glEnable (GL_POLYGON_STIPPLE);
   glPolygonStipple (fly);
   glRectf (125.0, 25.0, 225.0, 125.0);
   glPolygonStipple (halftone);
   glRectf (225.0, 25.0, 325.0, 125.0);
   glDisable (GL_POLYGON_STIPPLE);

   glFlush ();
}

void init (void)
{
   glClearColor (0.0, 0.0, 0.0, 0.0);
   glShadeModel (GL_FLAT);
}

void reshape (int w, int h)
{
   glViewport (0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode (GL_PROJECTION);
   glLoadIdentity ();
   gluOrtho2D (0.0, (GLdouble) w, 0.0, (GLdouble) h);
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
   glutInitWindowSize (350, 150);
   glutCreateWindow (argv[0]);
   init ();
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutMainLoop();
   return 0;
}

You might want to use display lists to store polygon stipple patterns to maximize efficiency. (See "Display-List Design Philosophy" in Chapter 7.)

Marking Polygon Boundary Edges

Advanced

OpenGL can render only convex polygons, but many nonconvex polygons arise in practice. To draw these nonconvex polygons, you typically subdivide them into convex polygons - usually triangles, as shown in Figure 2-12 - and then draw the triangles. Unfortunately, if you decompose a general polygon into triangles and draw the triangles, you can't really use glPolygonMode() to draw the polygon's outline, since you get all the triangle outlines inside it. To solve this problem, you can tell OpenGL whether a particular vertex precedes a boundary edge; OpenGL keeps track of this information by passing along with each vertex a bit indicating whether that vertex is followed by a boundary edge. Then, when a polygon is drawn in GL_LINE mode, the nonboundary edges aren't drawn. In Figure 2-12, the dashed lines represent added edges.

Figure 2-12 : Subdividing a Nonconvex Polygon

By default, all vertices are marked as preceding a boundary edge, but you can manually control the setting of the edge flag with the command glEdgeFlag*(). This command is used between glBegin() and glEnd() pairs, and it affects all the vertices specified after it until the next glEdgeFlag() call is made. It applies only to vertices specified for polygons, triangles, and quads, not to those specified for strips of triangles or quads.

void glEdgeFlag(GLboolean flag);
void glEdgeFlagv(const GLboolean *flag);
Indicates whether a vertex should be considered as initializing a boundary edge of a polygon. If flag is GL_TRUE, the edge flag is set to TRUE (the default), and any vertices created are considered to precede boundary edges until this function is called again with flag being GL_FALSE.

As an example, Example 2-7 draws the outline shown in Figure 2-13.

Figure 2-13 : Outlined Polygon Drawn Using Edge Flags

Example 2-7 : Marking Polygon Boundary Edges

glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glBegin(GL_POLYGON);
    glEdgeFlag(GL_TRUE);
    glVertex3fv(V0);
    glEdgeFlag(GL_FALSE);
    glVertex3fv(V1);
    glEdgeFlag(GL_TRUE);
    glVertex3fv(V2);
glEnd();

Normal Vectors

A normal vector (or normal, for short) is a vector that points in a direction that's perpendicular to a surface. For a flat surface, one perpendicular direction is the same for every point on the surface, but for a general curved surface, the normal direction might be different at each point on the surface. With OpenGL, you can specify a normal for each polygon or for each vertex. Vertices of the same polygon might share the same normal (for a flat surface) or have different normals (for a curved surface). But you can't assign normals anywhere other than at the vertices.

An object's normal vectors define the orientation of its surface in space - in particular, its orientation relative to light sources. These vectors are used by OpenGL to determine how much light the object receives at its vertices. Lighting - a large topic by itself - is the subject of Chapter 5, and you might want to review the following information after you've read that chapter. Normal vectors are discussed briefly here because you define normal vectors for an object at the same time you define the object's geometry.

You use glNormal*() to set the current normal to the value of the argument passed in. Subsequent calls to glVertex*() cause the specified vertices to be assigned the current normal. Often, each vertex has a different normal, which necessitates a series of alternating calls, as in Example 2-8.

Example 2-8 : Surface Normals at Vertices

glBegin (GL_POLYGON);
   glNormal3fv(n0);
   glVertex3fv(v0);
   glNormal3fv(n1);
   glVertex3fv(v1);
   glNormal3fv(n2);
   glVertex3fv(v2);
   glNormal3fv(n3);
   glVertex3fv(v3);
glEnd();
void glNormal3{bsidf}(TYPEnx, TYPEny, TYPEnz);
void glNormal3{bsidf}v(const TYPE *v);
Sets the current normal vector as specified by the arguments. The nonvector version (without the v) takes three arguments, which specify an (nx, ny, nz) vector that's taken to be the normal. Alternatively, you can use the vector version of this function (with the v) and supply a single array of three elements to specify the desired normal. The b, s, and i versions scale their parameter values linearly to the range [-1.0,1.0].

There's no magic to finding the normals for an object - most likely, you have to perform some calculations that might include taking derivatives - but there are several techniques and tricks you can use to achieve certain effects. Appendix E explains how to find normal vectors for surfaces. If you already know how to do this, if you can count on always being supplied with normal vectors, or if you don't want to use the lighting facility provided by OpenGL lighting facility, you don't need to read this appendix.

Note that at a given point on a surface, two vectors are perpendicular to the surface, and they point in opposite directions. By convention, the normal is the one that points to the outside of the surface being modeled. (If you get inside and outside reversed in your model, just change every normal vector from (x, y, z) to (- &xgr; , -y, -z)).

Also, keep in mind that since normal vectors indicate direction only, their length is mostly irrelevant. You can specify normals of any length, but eventually they have to be converted to having a length of 1 before lighting calculations are performed. (A vector that has a length of 1 is said to be of unit length, or normalized.) In general, you should supply normalized normal vectors. To make a normal vector of unit length, divide each of its x, y, z components by the length of the normal:

Normal vectors remain normalized as long as your model transformations include only rotations and translations. (See Chapter 3 for a discussion of transformations.) If you perform irregular transformations (such as scaling or multiplying by a shear matrix), or if you specify nonunit-length normals, then you should have OpenGL automatically normalize your normal vectors after the transformations. To do this, call glEnable() with GL_NORMALIZE as its argument. By default, automatic normalization is disabled. Note that automatic normalization typically requires additional calculations that might reduce the performance of your application.

Vertex Arrays

You may have noticed that OpenGL requires many function calls to render geometric primitives. Drawing a 20-sided polygon requires 22 function calls: one call to glBegin(), one call for each of the vertices, and a final call to glEnd(). In the two previous code examples, additional information (polygon boundary edge flags or surface normals) added function calls for each vertex. This can quickly double or triple the number of function calls required for one geometric object. For some systems, function calls have a great deal of overhead and can hinder performance.

An additional problem is the redundant processing of vertices that are shared between adjacent polygons. For example, the cube in Figure 2-14 has six faces and eight shared vertices. Unfortunately, using the standard method of describing this object, each vertex would have to be specified three times: once for every face that uses it. So 24 vertices would be processed, even though eight would be enough.

Figure 2-14 : Six Sides; Eight Shared Vertices

OpenGL has vertex array routines that allow you to specify a lot of vertex-related data with just a few arrays and to access that data with equally few function calls. Using vertex array routines, all 20 vertices in a 20-sided polygon could be put into one array and called with one function. If each vertex also had a surface normal, all 20 surface normals could be put into another array and also called with one function.

Arranging data in vertex arrays may increase the performance of your application. Using vertex arrays reduces the number of function calls, which improves performance. Also, using vertex arrays may allow non-redundant processing of shared vertices. (Vertex sharing is not supported on all implementations of OpenGL.)

Note: Vertex arrays are standard in version 1.1 of OpenGL but were not part of the OpenGL 1.0 specification. With OpenGL 1.0, some vendors have implemented vertex arrays as an extension.

There are three steps to using vertex arrays to render geometry.

Activate (enable) up to six arrays, each to store a different type of data: vertex coordinates, RGBA colors, color indices, surface normals, texture coordinates, or polygon edge flags.

Put data into the array or arrays. The arrays are accessed by the addresses of (that is, pointers to) their memory locations. In the client-server model, this data is stored in the client's address space.

Draw geometry with the data. OpenGL obtains the data from all activated arrays by dereferencing the pointers. In the client-server model, the data is transferred to the server's address space. There are three ways to do this:

Accessing individual array elements (randomly hopping around)

Creating a list of individual array elements (methodically hopping around)

Processing sequential array elements

The dereferencing method you choose may depend upon the type of problem you encounter.

Interleaved vertex array data is another common method of organization. Instead of having up to six different arrays, each maintaining a different type of data (color, surface normal, coordinate, and so on), you might have the different types of data mixed into a single array. (See "Interleaved Arrays" for two methods of solving this.)

Step 1: Enabling Arrays

The first step is to call glEnableClientState() with an enumerated parameter, which activates the chosen array. In theory, you may need to call this up to six times to activate the six available arrays. In practice, you'll probably activate only between one to four arrays. For example, it is unlikely that you would activate both GL_COLOR_ARRAY and GL_INDEX_ARRAY, since your program's display mode supports either RGBA mode or color-index mode, but probably not both simultaneously.

void glEnableClientState(GLenum array)
Specifies the array to enable. Symbolic constants GL_VERTEX_ARRAY, GL_COLOR_ARRAY, GL_INDEX_ARRAY, GL_NORMAL_ARRAY, GL_TEXTURE_COORD_ARRAY, and GL_EDGE_FLAG_ARRAY are acceptable parameters.

If you use lighting, you may want to define a surface normal for every vertex. (See "Normal Vectors.") To use vertex arrays for that case, you activate both the surface normal and vertex coordinate arrays:

glEnableClientState(GL_NORMAL_ARRAY);
glEnableClientState(GL_VERTEX_ARRAY);

Suppose that you want to turn off lighting at some point and just draw the geometry using a single color. You want to call glDisable() to turn off lighting states (see Chapter 5). Now that lighting has been deactivated, you also want to stop changing the values of the surface normal state, which is wasted effort. To do that, you call

glDisableClientState(GL_NORMAL_ARRAY);
void glDisableClientState(GLenum array);
Specifies the array to disable. Accepts the same symbolic constants as glEnableClientState().

You might be asking yourself why the architects of OpenGL created these new (and long!) command names, gl*ClientState(). Why can't you just call glEnable() and glDisable()? One reason is that glEnable() and glDisable() can be stored in a display list, but the specification of vertex arrays cannot, because the data remains on the client's side.

Step 2: Specifying Data for the Arrays

There is a straightforward way by which a single command specifies a single array in the client space. There are six different routines to specify arrays - one routine for each kind of array. There is also a command that can specify several client-space arrays at once, all originating from a single interleaved array.

void glVertexPointer(GLint size, GLenum type, GLsizei stride,
const GLvoid *pointer);
Specifies where spatial coordinate data can be accessed. pointer is the memory address of the first coordinate of the first vertex in the array. type specifies the data type (GL_SHORT, GL_INT, GL_FLOAT, or GL_DOUBLE) of each coordinate in the array. size is the number of coordinates per vertex, which must be 2, 3, or 4. stride is the byte offset between consecutive vertexes. If stride is 0, the vertices are understood to be tightly packed in the array.

To access the other five arrays, there are five similar routines:

void glColorPointer(GLint size, GLenum type, GLsizei stride,
const GLvoid *pointer);
void glIndexPointer(GLenum type, GLsizei stride, const GLvoid *pointer);
void glNormalPointer(GLenum type, GLsizei stride,
const GLvoid *pointer);
void glTexCoordPointer(GLint size, GLenum type, GLsizei stride,
const GLvoid *pointer);
void glEdgeFlagPointer(GLsizei stride, const GLvoid *pointer);

The main differences among the routines are whether size and type are unique or must be specified. For example, a surface normal always has three components, so it is redundant to specify its size. An edge flag is always a single Boolean, so neither size nor type needs to be mentioned. Table 2-4 displays legal values for size and data types.

Table 2-4 : Vertex Array Sizes (Values per Vertex) and Data Types(continued)

Command | Sizes | Values for type Argument
glVertexPointer | 2, 3, 4 | GL_SHORT, GL_INT, GL_FLOAT, GL_DOUBLE
glNormalPointer | 3 | GL_BYTE, GL_SHORT, GL_INT, GL_FLOAT, GL_DOUBLE
glColorPointer | 3, 4 | GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT, GL_FLOAT, GL_DOUBLE
glIndexPointer | 1 | GL_UNSIGNED_BYTE, GL_SHORT, GL_INT, GL_FLOAT, GL_DOUBLE
glTexCoordPointer | 1, 2, 3, 4 | GL_SHORT, GL_INT, GL_FLOAT, GL_DOUBLE
glEdgeFlagPointer | 1 | no type argument (type of data must be GLboolean)

Example 2-9 uses vertex arrays for both RGBA colors and vertex coordinates. RGB floating-point values and their corresponding (x, y) integer coordinates are loaded into the GL_COLOR_ARRAY and GL_VERTEX_ARRAY.

Example 2-9 : Enabling and Loading Vertex Arrays: varray.c

static GLint vertices[] = {25, 25,
                          100, 325,
                          175, 25,
                          175, 325,
                          250, 25,
                          325, 325};
static GLfloat colors[] = {1.0, 0.2, 0.2,
                          0.2, 0.2, 1.0,
                          0.8, 1.0, 0.2,
                          0.75, 0.75, 0.75,
                          0.35, 0.35, 0.35,
                          0.5, 0.5, 0.5};

glEnableClientState (GL_COLOR_ARRAY);
glEnableClientState (GL_VERTEX_ARRAY);

glColorPointer (3, GL_FLOAT, 0, colors);
glVertexPointer (2, GL_INT, 0, vertices);

Stride

With a stride of zero, each type of vertex array (RGB color, color index, vertex coordinate, and so on) must be tightly packed. The data in the array must be homogeneous; that is, the data must be all RGB color values, all vertex coordinates, or all some other data similar in some fashion.

Using a stride of other than zero can be useful, especially when dealing with interleaved arrays. In the following array of GLfloats, there are six vertices. For each vertex, there are three RGB color values, which alternate with the (x, y, z) vertex coordinates.

static GLfloat intertwined[] =
      {1.0, 0.2, 1.0, 100.0, 100.0, 0.0,
       1.0, 0.2, 0.2, 0.0, 200.0, 0.0,
       1.0, 1.0, 0.2, 100.0, 300.0, 0.0,
       0.2, 1.0, 0.2, 200.0, 300.0, 0.0,
       0.2, 1.0, 1.0, 300.0, 200.0, 0.0,
       0.2, 0.2, 1.0, 200.0, 100.0, 0.0};

Stride allows a vertex array to access its desired data at regular intervals in the array. For example, to reference only the color values in the intertwined array, the following call starts from the beginning of the array (which could also be passed as &intertwined[0]) and jumps ahead 6 * sizeof(GLfloat) bytes, which is the size of both the color and vertex coordinate values. This jump is enough to get to the beginning of the data for the next vertex.

glColorPointer (3, GL_FLOAT, 6 * sizeof(GLfloat), intertwined);

For the vertex coordinate pointer, you need to start from further in the array, at the fourth element of intertwined (remember that C programmers start counting at zero).

glVertexPointer(3, GL_FLOAT,6*sizeof(GLfloat), &intertwined[3]);

Step 3: Dereferencing and Rendering

Until the contents of the vertex arrays are dereferenced, the arrays remain on the client side, and their contents are easily changed. In Step 3, contents of the arrays are obtained, sent down to the server, and then sent down the graphics processing pipeline for rendering.

There are three ways to obtain data: from a single array element (indexed location), from a sequence of array elements, and from an ordered list of array elements.

Dereference a Single Array Element

void glArrayElement(GLint ith)
Obtains the data of one (the ith) vertex for all currently enabled arrays. For the vertex coordinate array, the corresponding command would be glVertex[size][type]v(), where size is one of [2,3,4], and type is one of [s,i,f,d] for GLshort, GLint, GLfloat, and GLdouble respectively. Both size and type were defined by glVertexPointer(). For other enabled arrays, glArrayElement() calls glEdgeFlagv(), glTexCoord[size][type]v(), glColor[size][type]v(), glIndex[type]v(), and glNormal[type]v(). If the vertex coordinate array is enabled, the glVertex*v() routine is executed last, after the execution (if enabled) of up to five corresponding array values.

glArrayElement() is usually called between glBegin() and glEnd(). (If called outside, glArrayElement() sets the current state for all enabled arrays, except for vertex, which has no current state.) In Example 2-10, a triangle is drawn using the third, fourth, and sixth vertices from enabled vertex arrays (again, remember that C programmers begin counting array locations with zero).

Example 2-10 : Using glArrayElement() to Define Colors and Vertices

glEnableClientState (GL_COLOR_ARRAY);
glEnableClientState (GL_VERTEX_ARRAY);
glColorPointer (3, GL_FLOAT, 0, colors);
glVertexPointer (2, GL_INT, 0, vertices);

glBegin(GL_TRIANGLES);
glArrayElement (2);
glArrayElement (3);
glArrayElement (5);
glEnd();

When executed, the latter five lines of code has the same effect as

glBegin(GL_TRIANGLES);
glColor3fv(colors+(2*3*sizeof(GLfloat));
glVertex3fv(vertices+(2*2*sizeof(GLint));
glColor3fv(colors+(3*3*sizeof(GLfloat));
glVertex3fv(vertices+(3*2*sizeof(GLint));
glColor3fv(colors+(5*3*sizeof(GLfloat));
glVertex3fv(vertices+(5*2*sizeof(GLint));
glEnd();

Since glArrayElement() is only a single function call per vertex, it may reduce the number of function calls, which increases overall performance.

Be warned that if the contents of the array are changed between glBegin() and glEnd(), there is no guarantee that you will receive original data or changed data for your requested element. To be safe, don't change the contents of any array element which might be accessed until the primitive is completed.

Dereference a List of Array Elements

glArrayElement() is good for randomly "hopping around" your data arrays. A similar routine, glDrawElements(), is good for hopping around your data arrays in a more orderly manner.

void glDrawElements(GLenum mode, GLsizei count, GLenum type,
void *indices);
Defines a sequence of geometric primitives using count number of elements, whose indices are stored in the array indices. type must be one of GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT, or GL_UNSIGNED_INT, indicating the data type of the indices array. mode specifies what kind of primitives are constructed and is one of the same values that is accepted by glBegin(); for example, GL_POLYGON, GL_LINE_LOOP, GL_LINES, GL_POINTS, and so on.

The effect of glDrawElements() is almost the same as this command sequence:

int i;
glBegin (mode);
for (i = 0; i < count; i++)
   glArrayElement(indices[i]);
glEnd();

glDrawElements() additionally checks to make sure mode, count, and type are valid. Also, unlike the preceding sequence, executing glDrawElements() leaves several states indeterminate. After execution of glDrawElements(), current RGB color, color index, normal coordinates, texture coordinates, and edge flag are indeterminate if the corresponding array has been enabled.

With glDrawElements(), the vertices for each face of the cube can be placed in an array of indices. Example 2-11 shows two ways to use glDrawElements() to render the cube. Figure 2-15 shows the numbering of the vertices used in Example 2-11.

Figure 2-15 : Cube with Numbered Vertices

Example 2-11 : Two Ways to Use glDrawElements()

static GLubyte frontIndices = {4, 5, 6, 7};
static GLubyte rightIndices = {1, 2, 6, 5};
static GLubyte bottomIndices = {0, 1, 5, 4};
static GLubyte backIndices = {0, 3, 2, 1};
static GLubyte leftIndices = {0, 4, 7, 3};
static GLubyte topIndices = {2, 3, 7, 6};

glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, frontIndices);
glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, rightIndices);
glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, bottomIndices);
glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, backIndices);
glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, leftIndices);
glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, topIndices);

Or better still, crunch all the indices together:

static GLubyte allIndices = {4, 5, 6, 7, 1, 2, 6, 5,
 0, 1, 5, 4, 0, 3, 2, 1,
 0, 4, 7, 3, 2, 3, 7, 6};

glDrawElements(GL_QUADS, 24, GL_UNSIGNED_BYTE, allIndices);

Note: It is an error to encapsulate glDrawElements() between a glBegin()/glEnd() pair.

With both glArrayElement() and glDrawElements(), it is also possible that your OpenGL implementation caches recently processed vertices, allowing your application to "share" or "reuse" vertices. Take the aforementioned cube, for example, which has six faces (polygons) but only eight vertices. Each vertex is used by exactly three faces. Without glArrayElement() or glDrawElements(), rendering all six faces would require processing twenty-four vertices, even though sixteen vertices would be redundant. Your implementation of OpenGL may be able to minimize redundancy and process as few as eight vertices. (Reuse of vertices may be limited to all vertices within a single glDrawElements() call or, for glArrayElement(), within one glBegin()/glEnd() pair.)

Dereference a Sequence of Array Elements

While glArrayElement() and glDrawElements() "hop around" your data arrays, glDrawArrays() plows straight through them.

void glDrawArrays(GLenum mode, GLint first, GLsizei count);
Constructs a sequence of geometric primitives using array elements starting at first and ending at first+count-1 of each enabled array. mode specifies what kinds of primitives are constructed and is one of the same values accepted by glBegin(); for example, GL_POLYGON, GL_LINE_LOOP, GL_LINES, GL_POINTS, and so on.

The effect of glDrawArrays() is almost the same as this command sequence:

int i;
glBegin (mode);
for (i = 0; i < count; i++)
   glArrayElement(first + i);
glEnd();

As is the case with glDrawElements(), glDrawArrays() also performs error checking on its parameter values and leaves the current RGB color, color index, normal coordinates, texture coordinates, and edge flag with indeterminate values if the corresponding array has been enabled.

Try This

  • Change the icosahedron drawing routine in Example 2-13 to use vertex arrays.

Interleaved Arrays

Advanced

Earlier in this chapter (in "Stride"), the special case of interleaved arrays was examined. In that section, the array intertwined, which interleaves RGB color and 3D vertex coordinates, was accessed by calls to glColorPointer() and glVertexPointer(). Careful use of stride helped properly specify the arrays.

static GLfloat intertwined[] =
      {1.0, 0.2, 1.0, 100.0, 100.0, 0.0,
       1.0, 0.2, 0.2, 0.0, 200.0, 0.0,
       1.0, 1.0, 0.2, 100.0, 300.0, 0.0,
       0.2, 1.0, 0.2, 200.0, 300.0, 0.0,
       0.2, 1.0, 1.0, 300.0, 200.0, 0.0,
       0.2, 0.2, 1.0, 200.0, 100.0, 0.0};

There is also a behemoth routine, glInterleavedArrays(), that can specify several vertex arrays at once. glInterleavedArrays() also enables and disables the appropriate arrays (so it combines both Steps 1 and 2). The array intertwined exactly fits one of the fourteen data interleaving configurations supported by glInterleavedArrays(). So to specify the contents of the array intertwined into the RGB color and vertex arrays and enable both arrays, call

glInterleavedArrays (GL_C3F_V3F, 0, intertwined);

This call to glInterleavedArrays() enables the GL_COLOR_ARRAY and GL_VERTEX_ARRAY arrays. It disables the GL_INDEX_ARRAY, GL_TEXTURE_COORD_ARRAY, GL_NORMAL_ARRAY, and GL_EDGE_FLAG_ARRAY.

This call also has the same effect as calling glColorPointer() and glVertexPointer() to specify the values for six vertices into each array. Now you are ready for Step 3: Calling glArrayElement(), glDrawElements(), or glDrawArrays() to dereference array elements.

void glInterleavedArrays(GLenum format, GLsizei stride, void *pointer)
Initializes all six arrays, disabling arrays that are not specified in format, and enabling the arrays that are specified. format is one of 14 symbolic constants, which represent 14 data configurations; Table 2-5 displays format values. stride specifies the byte offset between consecutive vertexes. If stride is 0, the vertexes are understood to be tightly packed in the array. pointer is the memory address of the first coordinate of the first vertex in the array.

Note that glInterleavedArrays() does not support edge flags.

The mechanics of glInterleavedArrays() are intricate and require reference to Example 2-12 and Table 2-5. In that example and table, you'll see et, ec, and en, which are the boolean values for the enabled or disabled texture coordinate, color, and normal arrays, and you'll see st, sc, and sv, which are the sizes (number of components) for the texture coordinate, color, and vertex arrays. tc is the data type for RGBA color, which is the only array that can have non-float interleaved values. pc, pn, and pv are the calculated strides for jumping over individual color, normal, and vertex values, and s is the stride (if one is not specified by the user) to jump from one array element to the next.

The effect of glInterleavedArrays() is the same as calling the command sequence in Example 2-12 with many values defined in Table 2-5. All pointer arithmetic is performed in units of sizeof(GL_UNSIGNED_BYTE).

Example 2-12 : Effect of glInterleavedArrays(format, stride, pointer)

int str;
/*  set et, ec, en, st, sc, sv, tc, pc, pn, pv, and s
 *  as a function of Table 2-5 and the value of format
 */
str = stride;
if (str == 0)
   str = s;
glDisableClientState(GL_EDGE_FLAG_ARRAY);
glDisableClientState(GL_INDEX_ARRAY);
if (et) {
   glEnableClientState(GL_TEXTURE_COORD_ARRAY);
   glTexCoordPointer(st, GL_FLOAT, str, pointer);
}
else
   glDisableClientState(GL_TEXTURE_COORD_ARRAY);
if (ec) {
   glEnableClientState(GL_COLOR_ARRAY);
   glColorPointer(sc, tc, str, pointer+pc);
}
else
   glDisableClientState(GL_COLOR_ARRAY);
if (en) {
   glEnableClientState(GL_NORMAL_ARRAY);
   glNormalPointer(GL_FLOAT, str, pointer+pn);
}
else
   glDisableClientState(GL_NORMAL_ARRAY);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(sv, GL_FLOAT, str, pointer+pv);

In Table 2-5, T and F are True and False. f is sizeof(GL_FLOAT). c is 4 times sizeof(GL_UNSIGNED_BYTE), rounded up to the nearest multiple of f.

Table 2-5 : (continued) Variables that Direct glInterleavedArrays()

format | et | ec | en | st | sc | sv | tc | pc | pn | pv | s
GL_V2F | F | F | F |  |  | 2 |  |  |  | 0 | 2f
GL_V3F | F | F | F |  |  | 3 |  |  |  | 0 | 3f
GL_C4UB_V2F | F | T | F |  | 4 | 2 | GL_UNSIGNED_BYTE | 0 |  | c | c+2f
GL_C4UB_V3F | F | T | F |  | 4 | 3 | GL_UNSIGNED_BYTE | 0 |  | c | c+3f
GL_C3F_V3F | F | T | F |  | 3 | 3 | GL_FLOAT | 0 |  | 3f | 6f
GL_N3F_V3F | F | F | T |  |  | 3 |  |  | 0 | 3f | 6f
GL_C4F_N3F_V3F | F | T | T |  | 4 | 3 | GL_FLOAT | 0 | 4f | 7f | 10f
GL_T2F_V3F | T | F | F | 2 |  | 3 |  |  |  | 2f | 5f
GL_T4F_V4F | T | F | F | 4 |  | 4 |  |  |  | 4f | 8f
GL_T2F_C4UB_V3F | T | T | F | 2 | 4 | 3 | GL_UNSIGNED_BYTE | 2f |  | c+2f | c+5f
GL_T2F_C3F_V3F | T | T | F | 2 | 3 | 3 | GL_FLOAT | 2f |  | 5f | 8f
GL_T2F_N3F_V3F | T | F | T | 2 |  | 3 |  |  | 2f | 5f | 8f
GL_T2F_C4F_N3F_V3F | T | T | T | 2 | 4 | 3 | GL_FLOAT | 2f | 6f | 9f | 12f
GL_T4F_C4F_N3F_V4F | T | T | T | 4 | 4 | 4 | GL_FLOAT | 4f | 8f | 11f | 15f

Start by learning the simpler formats, GL_V2F, GL_V3F, and GL_C3F_V3F. If you use any of the formats with C4UB, you may have to use a struct data type or do some delicate type casting and pointer math to pack four unsigned bytes into a single 32-bit word.

For some OpenGL implementations, use of interleaved arrays may increase application performance. With an interleaved array, the exact layout of your data is known. You know your data is tightly packed and may be accessed in one chunk. If interleaved arrays are not used, the stride and size information has to be examined to detect whether data is tightly packed.

Note: glInterleavedArrays() only enables and disables vertex arrays and specifies values for the vertex-array data. It does not render anything. You must still complete Step 3 and call glArrayElement(), glDrawElements(), or glDrawArrays() to dereference the pointers and render graphics.

Attribute Groups

In "Basic State Management," you saw how to set or query an individual state or state variable. Well, you can also save and restore the values of a collection of related state variables with a single command.

OpenGL groups related state variables into an attribute group. For example, the GL_LINE_BIT attribute consists of five state variables: the line width, the GL_LINE_STIPPLE enable status, the line stipple pattern, the line stipple repeat counter, and the GL_LINE_SMOOTH enable status. (See "Antialiasing" in Chapter 6.) With the commands glPushAttrib() and glPopAttrib(), you can save and restore all five state variables, all at once.

Some state variables are in more than one attribute group. For example, the state variable, GL_CULL_FACE, is part of both the polygon and the enable attribute groups.

In OpenGL Version 1.1, there are now two different attribute stacks. In addition to the original attribute stack (which saves the values of server state variables), there is also a client attribute stack, accessible by the commands glPushClientAttrib() and glPopClientAttrib().

In general, it's faster to use these commands than to get, save, and restore the values yourself. Some values might be maintained in the hardware, and getting them might be expensive. Also, if you're operating on a remote client, all the attribute data has to be transferred across the network connection and back as it is obtained, saved, and restored. However, your OpenGL implementation keeps the attribute stack on the server, avoiding unnecessary network delays.

There are about twenty different attribute groups, which can be saved and restored by glPushAttrib() and glPopAttrib(). There are two client attribute groups, which can be saved and restored by glPushClientAttrib() and glPopClientAttrib(). For both server and client, the attributes are stored on a stack, which has a depth of at least 16 saved attribute groups. (The actual stack depths for your implementation can be obtained using GL_MAX_ATTRIB_STACK_DEPTH and GL_MAX_CLIENT_ATTRIB_STACK_DEPTH with glGetIntegerv().) Pushing a full stack or popping an empty one generates an error.

(See the tables in Appendix B to find out exactly which attributes are saved for particular mask values; that is, which attributes are in a particular attribute group.)

void glPushAttrib(GLbitfield mask);
void glPopAttrib(void);
glPushAttrib() saves all the attributes indicated by bits in mask by pushing them onto the attribute stack. glPopAttrib() restores the values of those state variables that were saved with the last glPushAttrib(). Table 2-7 lists the possible mask bits that can be logically ORed together to save any combination of attributes. Each bit corresponds to a collection of individual state variables. For example, GL_LIGHTING_BIT refers to all the state variables related to lighting, which include the current material color, the ambient, diffuse, specular, and emitted light, a list of the lights that are enabled, and the directions of the spotlights. When glPopAttrib() is called, all those variables are restored.

The special mask, GL_ALL_ATTRIB_BITS, is used to save and restore all the state variables in all the attribute groups.

Table 2-6 : (continued) Attribute Groups

Mask Bit | Attribute Group
GL_ACCUM_BUFFER_BIT | accum-buffer
GL_ALL_ATTRIB_BITS | --
GL_COLOR_BUFFER_BIT | color-buffer
GL_CURRENT_BIT | current
GL_DEPTH_BUFFER_BIT | depth-buffer
GL_ENABLE_BIT | enable
GL_EVAL_BIT | eval
GL_FOG_BIT | fog
GL_HINT_BIT | hint
GL_LIGHTING_BIT | lighting
GL_LINE_BIT | line
GL_LIST_BIT | list
GL_PIXEL_MODE_BIT | pixel
GL_POINT_BIT | point
GL_POLYGON_BIT | polygon
GL_POLYGON_STIPPLE_BIT | polygon-stipple
GL_SCISSOR_BIT | scissor
GL_STENCIL_BUFFER_BIT | stencil-buffer
GL_TEXTURE_BIT | texture
GL_TRANSFORM_BIT | transform
GL_VIEWPORT_BIT | viewport

void glPushClientAttrib(GLbitfield mask);
void glPopClientAttrib(void);
glPushClientAttrib() saves all the attributes indicated by bits in mask by pushing them onto the client attribute stack. glPopClientAttrib() restores the values of those state variables that were saved with the last glPushClientAttrib(). Table 2-7 lists the possible mask bits that can be logically ORed together to save any combination of client attributes.
There are two client attribute groups, feedback and select, that cannot be saved or restored with the stack mechanism.
Table 2-7 : Client Attribute Groups
Mask Bit | Attribute Group
GL_CLIENT_PIXEL_STORE_BIT | pixel-store
GL_CLIENT_VERTEX_ARRAY_BIT | vertex-array
GL_ALL_CLIENT_ATTRIB_BITS | --
can't be pushed or popped | feedback
can't be pushed or popped | select

Some Hints for Building Polygonal Models of Surfaces

Following are some techniques that you might want to use as you build polygonal approximations of surfaces. You might want to review this section after you've read Chapter 5 on lighting and Chapter 7 on display lists. The lighting conditions affect how models look once they're drawn, and some of the following techniques are much more efficient when used in conjunction with display lists. As you read these techniques, keep in mind that when lighting calculations are enabled, normal vectors must be specified to get proper results.

Constructing polygonal approximations to surfaces is an art, and there is no substitute for experience. This section, however, lists a few pointers that might make it a bit easier to get started.

  • Keep polygon orientations consistent. Make sure that when viewed from the outside, all the polygons on the surface are oriented in the same direction (all clockwise or all counterclockwise). Consistent orientation is important for polygon culling and two-sided lighting. Try to get this right the first time, since it's excruciatingly painful to fix the problem later. (If you use glScale*() to reflect geometry around some axis of symmetry, you might change the orientation with glFrontFace() to keep the orientations consistent.)
  • When you subdivide a surface, watch out for any nontriangular polygons. The three vertices of a triangle are guaranteed to lie on a plane; any polygon with four or more vertices might not. Nonplanar polygons can be viewed from some orientation such that the edges cross each other, and OpenGL might not render such polygons correctly.
  • There's always a trade-off between the display speed and the quality of the image. If you subdivide a surface into a small number of polygons, it renders quickly but might have a jagged appearance; if you subdivide it into millions of tiny polygons, it probably looks good but might take a long time to render. Ideally, you can provide a parameter to the subdivision routines that indicates how fine a subdivision you want, and if the object is farther from the eye, you can use a coarser subdivision. Also, when you subdivide, use large polygons where the surface is relatively flat, and small polygons in regions of high curvature.
  • For high-quality images, it's a good idea to subdivide more on the silhouette edges than in the interior. If the surface is to be rotated relative to the eye, this is tougher to do, since the silhouette edges keep moving. Silhouette edges occur where the normal vectors are perpendicular to the vector from the surface to the viewpoint - that is, when their vector dot product is zero. Your subdivision algorithm might choose to subdivide more if this dot product is near zero.
  • Try to avoid T-intersections in your models (see Figure 2-16). As shown, there's no guarantee that the line segments AB and BC lie on exactly the same pixels as the segment AC. Sometimes they do, and sometimes they don't, depending on the transformations and orientation. This can cause cracks to appear intermittently in the surface.

Figure 2-16 : Modifying an Undesirable T-intersection

  • If you're constructing a closed surface, make sure to use exactly the same numbers for coordinates at the beginning and end of a closed loop, or you can get gaps and cracks due to numerical round-off. Here's a two-dimensional example of bad code:
/* don't use this code */
#define PI 3.14159265
#define EDGES 30

/* draw a circle */
glBegin(GL_LINE_STRIP);
for (i = 0; i <= EDGES; i++)
    glVertex2f(cos((2*PI*i)/EDGES), sin((2*PI*i)/EDGES));
glEnd();

The edges meet exactly only if your machine manages to calculate the sine and cosine of 0 and of (2*PI*EDGES/EDGES) and gets exactly the same values. If you trust the floating-point unit on your machine to do this right, the authors have a bridge they'd like to sell you.... To correct the code, make sure that when i == EDGES, you use 0 for the sine and cosine, not 2*PI*EDGES/EDGES. (Or simpler still, use GL_LINE_LOOP instead of GL_LINE_STRIP, and change the loop termination condition to i < EDGES.)

An Example: Building an Icosahedron

To illustrate some of the considerations that arise in approximating a surface, let's look at some example code sequences. This code concerns the vertices of a regular icosahedron (which is a Platonic solid composed of twenty faces that span twelve vertices, each face of which is an equilateral triangle). An icosahedron can be considered a rough approximation for a sphere. Example 2-13 defines the vertices and triangles making up an icosahedron and then draws the icosahedron.

Example 2-13 : Drawing an Icosahedron

#define X .525731112119133606
#define Z .850650808352039932

static GLfloat vdata[12][3] = {
   {-X, 0.0, Z}, {X, 0.0, Z}, {-X, 0.0, -Z}, {X, 0.0, -Z},
   {0.0, Z, X}, {0.0, Z, -X}, {0.0, -Z, X}, {0.0, -Z, -X},
   {Z, X, 0.0}, {-Z, X, 0.0}, {Z, -X, 0.0}, {-Z, -X, 0.0}
};
static GLuint tindices[20][3] = {
   {0,4,1}, {0,9,4}, {9,5,4}, {4,5,8}, {4,8,1},
   {8,10,1}, {8,3,10}, {5,3,8}, {5,2,3}, {2,7,3},
   {7,10,3}, {7,6,10}, {7,11,6}, {11,0,6}, {0,1,6},
   {6,1,10}, {9,0,11}, {9,11,2}, {9,2,5}, {7,2,11} };
int i;

glBegin(GL_TRIANGLES);
for (i = 0; i < 20; i++) {
   /* color information here */
   glVertex3fv(&vdata[tindices[i][0]][0]);
   glVertex3fv(&vdata[tindices[i][1]][0]);
   glVertex3fv(&vdata[tindices[i][2]][0]);
}
glEnd();

The strange numbers X and Z are chosen so that the distance from the origin to any of the vertices of the icosahedron is 1.0. The coordinates of the twelve vertices are given in the array vdata[][], where the zeroth vertex is {- &Xgr; , 0.0, &Zgr; }, the first is {X, 0.0, Z}, and so on. The array tindices[][] tells how to link the vertices to make triangles. For example, the first triangle is made from the zeroth, fourth, and first vertex. If you take the vertices for triangles in the order given, all the triangles have the same orientation.

The line that mentions color information should be replaced by a command that sets the color of the ith face. If no code appears here, all faces are drawn in the same color, and it'll be impossible to discern the three-dimensional quality of the object. An alternative to explicitly specifying colors is to define surface normals and use lighting, as described in the next subsection.

Note: In all the examples described in this section, unless the surface is to be drawn only once, you should probably save the calculated vertex and normal coordinates so that the calculations don't need to be repeated each time that the surface is drawn. This can be done using your own data structures or by constructing display lists. (See Chapter 7.)

Calculating Normal Vectors for a Surface

If a surface is to be lit, you need to supply the vector normal to the surface. Calculating the normalized cross product of two vectors on that surface provides normal vector. With the flat surfaces of an icosahedron, all three vertices defining a surface have the same normal vector. In this case, the normal needs to be specified only once for each set of three vertices. The code in Example 2-14 can replace the "color information here" line in Example 2-13 for drawing the icosahedron.

Example 2-14 : Generating Normal Vectors for a Surface

GLfloat d1[3], d2[3], norm[3];
for (j = 0; j < 3; j++) {
   d1[j] = vdata[tindices[i][0]][j] - vdata[tindices[i][1]][j];
   d2[j] = vdata[tindices[i][1]][j] - vdata[tindices[i][2]][j];
}
normcrossprod(d1, d2, norm);
glNormal3fv(norm);

The function normcrossprod() produces the normalized cross product of two vectors, as shown in Example 2-15.

Example 2-15 : Calculating the Normalized Cross Product of Two Vectors

void normalize(float v[3]) {
   GLfloat d = sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]);
   if (d == 0.0) {
      error("zero length vector");
      return;
   }
   v[0] /= d; v[1] /= d; v[2] /= d;
}

void normcrossprod(float v1[3], float v2[3], float out[3])
{
   GLint i, j;
   GLfloat length;

   out[0] = v1[1]*v2[2] - v1[2]*v2[1];
   out[1] = v1[2]*v2[0] - v1[0]*v2[2];
   out[2] = v1[0]*v2[1] - v1[1]*v2[0];
   normalize(out);
}

If you're using an icosahedron as an approximation for a shaded sphere, you'll want to use normal vectors that are perpendicular to the true surface of the sphere, rather than being perpendicular to the faces. For a sphere, the normal vectors are simple; each points in the same direction as the vector from the origin to the corresponding vertex. Since the icosahedron vertex data is for an icosahedron of radius 1, the normal and vertex data is identical. Here is the code that would draw an icosahedral approximation of a smoothly shaded sphere (assuming that lighting is enabled, as described in Chapter 5):

glBegin(GL_TRIANGLES);
for (i = 0; i < 20; i++) {
      glNormal3fv(&vdata[tindices[i][0]][0]);
      glVertex3fv(&vdata[tindices[i][0]][0]);
      glNormal3fv(&vdata[tindices[i][1]][0]);
      glVertex3fv(&vdata[tindices[i][1]][0]);
      glNormal3fv(&vdata[tindices[i][2]][0]);
      glVertex3fv(&vdata[tindices[i][2]][0]);
}
glEnd();

Improving the Model

A twenty-sided approximation to a sphere doesn't look good unless the image of the sphere on the screen is quite small, but there's an easy way to increase the accuracy of the approximation. Imagine the icosahedron inscribed in a sphere, and subdivide the triangles as shown in Figure 2-17. The newly introduced vertices lie slightly inside the sphere, so push them to the surface by normalizing them (dividing them by a factor to make them have length 1). This subdivision process can be repeated for arbitrary accuracy. The three objects shown in Figure 2-17 use 20, 80, and 320 approximating triangles, respectively.

Figure 2-17 : Subdividing to Improve a Polygonal Approximation to a Surface

Example 2-16 performs a single subdivision, creating an 80-sided spherical approximation.

Example 2-16 : Single Subdivision

void drawtriangle(float *v1, float *v2, float *v3)
{
   glBegin(GL_TRIANGLES);
      glNormal3fv(v1); vlVertex3fv(v1);
      glNormal3fv(v2); vlVertex3fv(v2);
      glNormal3fv(v3); vlVertex3fv(v3);
   glEnd();
}

void subdivide(float *v1, float *v2, float *v3)
{
   GLfloat v12[3], v23[3], v31[3];
   GLint i;

   for (i = 0; i < 3; i++) {
      v12[i] = v1[i]+v2[i];
      v23[i] = v2[i]+v3[i];
      v31[i] = v3[i]+v1[i];
   }
   normalize(v12);
   normalize(v23);
   normalize(v31);
   drawtriangle(v1, v12, v31);
   drawtriangle(v2, v23, v12);
   drawtriangle(v3, v31, v23);
   drawtriangle(v12, v23, v31);
}

for (i = 0; i < 20; i++) {
   subdivide(&vdata[tindices[i][0]][0],
             &vdata[tindices[i][1]][0],
             &vdata[tindices[i][2]][0]);
}

Example 2-17 is a slight modification of Example 2-16 which recursively subdivides the triangles to the proper depth. If the depth value is 0, no subdivisions are performed, and the triangle is drawn as is. If the depth is 1, a single subdivision is performed, and so on.

Example 2-17 : Recursive Subdivision

void subdivide(float *v1, float *v2, float *v3, long depth)
{
   GLfloat v12[3], v23[3], v31[3];
   GLint i;

   if (depth == 0) {
      drawtriangle(v1, v2, v3);
      return;
   }
   for (i = 0; i < 3; i++) {
      v12[i] = v1[i]+v2[i];
      v23[i] = v2[i]+v3[i];
      v31[i] = v3[i]+v1[i];
   }
   normalize(v12);
   normalize(v23);
   normalize(v31);
   subdivide(v1, v12, v31, depth-1);
   subdivide(v2, v23, v12, depth-1);
   subdivide(v3, v31, v23, depth-1);
   subdivide(v12, v23, v31, depth-1);
}

Generalized Subdivision

A recursive subdivision technique such as the one described in Example 2-17 can be used for other types of surfaces. Typically, the recursion ends either if a certain depth is reached or if some condition on the curvature is satisfied (highly curved parts of surfaces look better with more subdivision).

To look at a more general solution to the problem of subdivision, consider an arbitrary surface parameterized by two variables u[0] and u[1]. Suppose that two routines are provided:

void surf(GLfloat u[2], GLfloat vertex[3], GLfloat normal[3]);
float curv(GLfloat u[2]);

If surf() is passed u[], the corresponding three-dimensional vertex and normal vectors (of length 1) are returned. If u[] is passed to curv(), the curvature of the surface at that point is calculated and returned. (See an introductory textbook on differential geometry for more information about measuring surface curvature.)

Example 2-18 shows the recursive routine that subdivides a triangle either until the maximum depth is reached or until the maximum curvature at the three vertices is less than some cutoff.

Example 2-18 : Generalized Subdivision

void subdivide(float u1[2], float u2[2], float u3[2],
                float cutoff, long depth)
{
   GLfloat v1[3], v2[3], v3[3], n1[3], n2[3], n3[3];
   GLfloat u12[2], u23[2], u32[2];
   GLint i;

   if (depth == maxdepth || (curv(u1) < cutoff &&
       curv(u2) < cutoff && curv(u3) < cutoff)) {
      surf(u1, v1, n1); surf(u2, v2, n2); surf(u3, v3, n3);
      glBegin(GL_POLYGON);
         glNormal3fv(n1); glVertex3fv(v1);
         glNormal3fv(n2); glVertex3fv(v2);
         glNormal3fv(n3); glVertex3fv(v3);
      glEnd();
      return;
   }
   for (i = 0; i < 2; i++) {
      u12[i] = (u1[i] + u2[i])/2.0;
      u23[i] = (u2[i] + u3[i])/2.0;
      u31[i] = (u3[i] + u1[i])/2.0;
   }
   subdivide(u1, u12, u31, cutoff, depth+1);
   subdivide(u2, u23, u12, cutoff, depth+1);
   subdivide(u3, u31, u23, cutoff, depth+1);
   subdivide(u12, u23, u31, cutoff, depth+1);
}


Chapter 3 - OpenGL Programming Guide

OpenGL Programming Guide
Programming Guide > Chapter 3

Chapter 3
Viewing

Chapter Objectives

After reading this chapter, you'll be able to do the following:

  • View a geometric model in any orientation by transforming it in three-dimensional space
  • Control the location in three-dimensional space from which the model is viewed
  • Clip undesired portions of the model out of the scene that's to be viewed
  • Manipulate the appropriate matrix stacks that control model transformation for viewing and project the model onto the screen
  • Combine multiple transformations to mimic sophisticated systems in motion, such as a solar system or an articulated robot arm
  • Reverse or mimic the operations of the geometric processing pipeline

Chapter 2 explained how to instruct OpenGL to draw the geometric models you want displayed in your scene. Now you must decide how you want to position the models in the scene, and you must choose a vantage point from which to view the scene. You can use the default positioning and vantage point, but most likely you want to specify them.

Look at the image on the cover of this book. The program that produced that image contained a single geometric description of a building block. Each block was carefully positioned in the scene: Some blocks were scattered on the floor, some were stacked on top of each other on the table, and some were assembled to make the globe. Also, a particular viewpoint had to be chosen. Obviously, we wanted to look at the corner of the room containing the globe. But how far away from the scene - and where exactly - should the viewer be? We wanted to make sure that the final image of the scene contained a good view out the window, that a portion of the floor was visible, and that all the objects in the scene were not only visible but presented in an interesting arrangement. This chapter explains how to use OpenGL to accomplish these tasks: how to position and orient models in three-dimensional space and how to establish the location - also in three-dimensional space - of the viewpoint. All of these factors help determine exactly what image appears on the screen.

You want to remember that the point of computer graphics is to create a two-dimensional image of three-dimensional objects (it has to be two-dimensional because it's drawn on a flat screen), but you need to think in three-dimensional coordinates while making many of the decisions that determine what gets drawn on the screen. A common mistake people make when creating three-dimensional graphics is to start thinking too soon that the final image appears on a flat, two-dimensional screen. Avoid thinking about which pixels need to be drawn, and instead try to visualize three-dimensional space. Create your models in some three-dimensional universe that lies deep inside your computer, and let the computer do its job of calculating which pixels to color.

A series of three computer operations convert an object's three-dimensional coordinates to pixel positions on the screen.

  • Transformations, which are represented by matrix multiplication, include modeling, viewing, and projection operations. Such operations include rotation, translation, scaling, reflecting, orthographic projection, and perspective projection. Generally, you use a combination of several transformations to draw a scene.
  • Since the scene is rendered on a rectangular window, objects (or parts of objects) that lie outside the window must be clipped. In three-dimensional computer graphics, clipping occurs by throwing out objects on one side of a clipping plane.
  • Finally, a correspondence must be established between the transformed coordinates and screen pixels. This is known as a viewport transformation.

This chapter describes all of these operations, and how to control them, in the following major sections:

  • "Overview: The Camera Analogy" gives an overview of the transformation process by describing the analogy of taking a photograph with a camera, presents a simple example program that transforms an object, and briefly describes the basic OpenGL transformation commands.
  • "Viewing and Modeling Transformations" explains in detail how to specify and to imagine the effect of viewing and modeling transformations. These transformations orient the model and the camera relative to each other to obtain the desired final image.
  • "Projection Transformations" describes how to specify the shape and orientation of the viewing volume. The viewing volume determines how a scene is projected onto the screen (with a perspective or orthographic projection) and which objects or parts of objects are clipped out of the scene.
  • "Viewport Transformation" explains how to control the conversion of three-dimensional model coordinates to screen coordinates.
  • "Troubleshooting Transformations" presents some tips for discovering why you might not be getting the desired effect from your modeling, viewing, projection, and viewport transformations.
  • "Manipulating the Matrix Stacks" discusses how to save and restore certain transformations. This is particularly useful when you're drawing complicated objects that are built up from simpler ones.
  • "Additional Clipping Planes" describes how to specify additional clipping planes beyond those defined by the viewing volume.
  • "Examples of Composing Several Transformations" walks you through a couple of more complicated uses for transformations.
  • "Reversing or Mimicking Transformations" shows you how to take a transformed point in window coordinates and reverse the transformation to obtain its original object coordinates. The transformation itself (without reversal) can also be emulated.

Overview: The Camera Analogy

The transformation process to produce the desired scene for viewing is analogous to taking a photograph with a camera. As shown in Figure 3-1, the steps with a camera (or a computer) might be the following.

Set up your tripod and pointing the camera at the scene (viewing transformation).

Arrange the scene to be photographed into the desired composition (modeling transformation).

Choose a camera lens or adjust the zoom (projection transformation).

Determine how large you want the final photograph to be - for example, you might want it enlarged (viewport transformation).

After these steps are performed, the picture can be snapped or the scene can be drawn.

Figure 3-1 : The Camera Analogy

Note that these steps correspond to the order in which you specify the desired transformations in your program, not necessarily the order in which the relevant mathematical operations are performed on an object's vertices. The viewing transformations must precede the modeling transformations in your code, but you can specify the projection and viewport transformations at any point before drawing occurs. Figure 3-2 shows the order in which these operations occur on your computer.

Figure 3-2 : Stages of Vertex Transformation

To specify viewing, modeling, and projection transformations, you construct a 4 ´ 4 matrix M, which is then multiplied by the coordinates of each vertex v in the scene to accomplish the transformation

v'=Mv

(Remember that vertices always have four coordinates (x, y, z, w), though in most cases w is 1 and for two-dimensional data z is 0.) Note that viewing and modeling transformations are automatically applied to surface normal vectors, in addition to vertices. (Normal vectors are used only in eye coordinates.) This ensures that the normal vector's relationship to the vertex data is properly preserved.

The viewing and modeling transformations you specify are combined to form the modelview matrix, which is applied to the incoming object coordinates to yield eye coordinates. Next, if you've specified additional clipping planes to remove certain objects from the scene or to provide cutaway views of objects, these clipping planes are applied.

After that, OpenGL applies the projection matrix to yield clip coordinates. This transformation defines a viewing volume; objects outside this volume are clipped so that they're not drawn in the final scene. After this point, the perspective division is performed by dividing coordinate values by w, to produce normalized device coordinates. (See Appendix F for more information about the meaning of the w coordinate and how it affects matrix transformations.) Finally, the transformed coordinates are converted to window coordinates by applying the viewport transformation. You can manipulate the dimensions of the viewport to cause the final image to be enlarged, shrunk, or stretched.

You might correctly suppose that the x and y coordinates are sufficient to determine which pixels need to be drawn on the screen. However, all the transformations are performed on the z coordinates as well. This way, at the end of this transformation process, the z values correctly reflect the depth of a given vertex (measured in distance away from the screen). One use for this depth value is to eliminate unnecessary drawing. For example, suppose two vertices have the same x and y values but different z values. OpenGL can use this information to determine which surfaces are obscured by other surfaces and can then avoid drawing the hidden surfaces. (See Chapter 10 for more information about this technique, which is called hidden-surface removal.)

As you've probably guessed by now, you need to know a few things about matrix mathematics to get the most out of this chapter. If you want to brush up on your knowledge in this area, you might consult a textbook on linear algebra.

A Simple Example: Drawing a Cube

Example 3-1 draws a cube that's scaled by a modeling transformation (see Figure 3-3). The viewing transformation, gluLookAt(), positions and aims the camera towards where the cube is drawn. A projection transformation and a viewport transformation are also specified. The rest of this section walks you through Example 3-1 and briefly explains the transformation commands it uses. The succeeding sections contain the complete, detailed discussion of all OpenGL's transformation commands.

Figure 3-3 : Transformed Cube

Example 3-1 : Transformed Cube: cube.c

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>

void init(void)
{
   glClearColor (0.0, 0.0, 0.0, 0.0);
   glShadeModel (GL_FLAT);
}

void display(void)
{
   glClear (GL_COLOR_BUFFER_BIT);
   glColor3f (1.0, 1.0, 1.0);
   glLoadIdentity ();             /* clear the matrix */
           /* viewing transformation  */
   gluLookAt (0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
   glScalef (1.0, 2.0, 1.0);      /* modeling transformation */
   glutWireCube (1.0);
   glFlush ();
}

void reshape (int w, int h)
{
   glViewport (0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode (GL_PROJECTION);
   glLoadIdentity ();
   glFrustum (-1.0, 1.0, -1.0, 1.0, 1.5, 20.0);
   glMatrixMode (GL_MODELVIEW);
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
   glutInitWindowSize (500, 500);
   glutInitWindowPosition (100, 100);
   glutCreateWindow (argv[0]);
   init ();
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutMainLoop();
   return 0;
}

The Viewing Transformation

Recall that the viewing transformation is analogous to positioning and aiming a camera. In this code example, before the viewing transformation can be specified, the current matrix is set to the identity matrix with glLoadIdentity(). This step is necessary since most of the transformation commands multiply the current matrix by the specified matrix and then set the result to be the current matrix. If you don't clear the current matrix by loading it with the identity matrix, you continue to combine previous transformation matrices with the new one you supply. In some cases, you do want to perform such combinations, but you also need to clear the matrix sometimes.

In Example 3-1, after the matrix is initialized, the viewing transformation is specified with gluLookAt(). The arguments for this command indicate where the camera (or eye position) is placed, where it is aimed, and which way is up. The arguments used here place the camera at (0, 0, 5), aim the camera lens towards (0, 0, 0), and specify the up-vector as (0, 1, 0). The up-vector defines a unique orientation for the camera.

If gluLookAt() was not called, the camera has a default position and orientation. By default, the camera is situated at the origin, points down the negative z-axis, and has an up-vector of (0, 1, 0). So in Example 3-1, the overall effect is that gluLookAt() moves the camera 5 units along the z-axis. (See "Viewing and Modeling Transformations" for more information about viewing transformations.)

The Modeling Transformation

You use the modeling transformation to position and orient the model. For example, you can rotate, translate, or scale the model - or perform some combination of these operations. In Example 3-1, glScalef() is the modeling transformation that is used. The arguments for this command specify how scaling should occur along the three axes. If all the arguments are 1.0, this command has no effect. In Example 3-1, the cube is drawn twice as large in the y direction. Thus, if one corner of the cube had originally been at (3.0, 3.0, 3.0), that corner would wind up being drawn at (3.0, 6.0, 3.0). The effect of this modeling transformation is to transform the cube so that it isn't a cube but a rectangular box.

Try This

Change the gluLookAt() call in Example 3-1 to the modeling transformation glTranslatef() with parameters (0.0, 0.0, -5.0). The result should look exactly the same as when you used gluLookAt(). Why are the effects of these two commands similar?

Note that instead of moving the camera (with a viewing transformation) so that the cube could be viewed, you could have moved the cube away from the camera (with a modeling transformation). This duality in the nature of viewing and modeling transformations is why you need to think about the effect of both types of transformations simultaneously. It doesn't make sense to try to separate the effects, but sometimes it's easier to think about them one way rather than the other. This is also why modeling and viewing transformations are combined into the modelview matrix before the transformations are applied. (See "Viewing and Modeling Transformations" for more detail on how to think about modeling and viewing transformations and how to specify them to get the result you want.)

Also note that the modeling and viewing transformations are included in the display() routine, along with the call that's used to draw the cube, glutWireCube(). This way, display() can be used repeatedly to draw the contents of the window if, for example, the window is moved or uncovered, and you've ensured that each time, the cube is drawn in the desired way, with the appropriate transformations. The potential repeated use of display() underscores the need to load the identity matrix before performing the viewing and modeling transformations, especially when other transformations might be performed between calls to display().

The Projection Transformation

Specifying the projection transformation is like choosing a lens for a camera. You can think of this transformation as determining what the field of view or viewing volume is and therefore what objects are inside it and to some extent how they look. This is equivalent to choosing among wide-angle, normal, and telephoto lenses, for example. With a wide-angle lens, you can include a wider scene in the final photograph than with a telephoto lens, but a telephoto lens allows you to photograph objects as though they're closer to you than they actually are. In computer graphics, you don't have to pay $10,000 for a 2000-millimeter telephoto lens; once you've bought your graphics workstation, all you need to do is use a smaller number for your field of view.

In addition to the field-of-view considerations, the projection transformation determines how objects are projected onto the screen, as its name suggests. Two basic types of projections are provided for you by OpenGL, along with several corresponding commands for describing the relevant parameters in different ways. One type is the perspective projection, which matches how you see things in daily life. Perspective makes objects that are farther away appear smaller; for example, it makes railroad tracks appear to converge in the distance. If you're trying to make realistic pictures, you'll want to choose perspective projection, which is specified with the glFrustum() command in this code example.

The other type of projection is orthographic, which maps objects directly onto the screen without affecting their relative size. Orthographic projection is used in architectural and computer-aided design applications where the final image needs to reflect the measurements of objects rather than how they might look. Architects create perspective drawings to show how particular buildings or interior spaces look when viewed from various vantage points; the need for orthographic projection arises when blueprint plans or elevations are generated, which are used in the construction of buildings. (See "Projection Transformations" for a discussion of ways to specify both kinds of projection transformations.)

Before glFrustum() can be called to set the projection transformation, some preparation needs to happen. As shown in the reshape() routine in Example 3-1, the command called glMatrixMode() is used first, with the argument GL_PROJECTION. This indicates that the current matrix specifies the projection transformation; the following transformation calls then affect the projection matrix. As you can see, a few lines later glMatrixMode() is called again, this time with GL_MODELVIEW as the argument. This indicates that succeeding transformations now affect the modelview matrix instead of the projection matrix. (See "Manipulating the Matrix Stacks" for more information about how to control the projection and modelview matrices.)

Note that glLoadIdentity() is used to initialize the current projection matrix so that only the specified projection transformation has an effect. Now glFrustum() can be called, with arguments that define the parameters of the projection transformation. In this example, both the projection transformation and the viewport transformation are contained in the reshape() routine, which is called when the window is first created and whenever the window is moved or reshaped. This makes sense, since both projecting (the width to height aspect ratio of the projection viewing volume) and applying the viewport relate directly to the screen, and specifically to the size or aspect ratio of the window on the screen.

Try This

Change the glFrustum() call in Example 3-1 to the more commonly used Utility Library routine gluPerspective() with parameters (60.0, 1.0, 1.5, 20.0). Then experiment with different values, especially for fovy and aspect.

The Viewport Transformation

Together, the projection transformation and the viewport transformation determine how a scene gets mapped onto the computer screen. The projection transformation specifies the mechanics of how the mapping should occur, and the viewport indicates the shape of the available screen area into which the scene is mapped. Since the viewport specifies the region the image occupies on the computer screen, you can think of the viewport transformation as defining the size and location of the final processed photograph - for example, whether the photograph should be enlarged or shrunk.

The arguments to glViewport() describe the origin of the available screen space within the window - (0, 0) in this example - and the width and height of the available screen area, all measured in pixels on the screen. This is why this command needs to be called within reshape() - if the window changes size, the viewport needs to change accordingly. Note that the width and height are specified using the actual width and height of the window; often, you want to specify the viewport this way rather than giving an absolute size. (See "Viewport Transformation" for more information about how to define the viewport.)

Drawing the Scene

Once all the necessary transformations have been specified, you can draw the scene (that is, take the photograph). As the scene is drawn, OpenGL transforms each vertex of every object in the scene by the modeling and viewing transformations. Each vertex is then transformed as specified by the projection transformation and clipped if it lies outside the viewing volume described by the projection transformation. Finally, the remaining transformed vertices are divided by w and mapped onto the viewport.

General-Purpose Transformation Commands

This section discusses some OpenGL commands that you might find useful as you specify desired transformations. You've already seen a couple of these commands, glMatrixMode() and glLoadIdentity(). The other two commands described here - glLoadMatrix*() and glMultMatrix*() - allow you to specify any transformation matrix directly and then to multiply the current matrix by that specified matrix. More specific transformation commands - such as gluLookAt() and glScale*() - are described in later sections.

As described in the preceding section, you need to state whether you want to modify the modelview or projection matrix before supplying a transformation command. You choose the matrix with glMatrixMode(). When you use nested sets of OpenGL commands that might be called repeatedly, remember to reset the matrix mode correctly. (The glMatrixMode() command can also be used to indicate the texture matrix; texturing is discussed in detail in "The Texture Matrix Stack" in Chapter 9.)

void glMatrixMode(GLenum mode);
Specifies whether the modelview, projection, or texture matrix will be modified, using the argument GL_MODELVIEW, GL_PROJECTION, or GL_TEXTURE for mode. Subsequent transformation commands affect the specified matrix. Note that only one matrix can be modified at a time. By default, the modelview matrix is the one that's modifiable, and all three matrices contain the identity matrix.

You use the glLoadIdentity() command to clear the currently modifiable matrix for future transformation commands, since these commands modify the current matrix. Typically, you always call this command before specifying projection or viewing transformations, but you might also call it before specifying a modeling transformation.

void glLoadIdentity(void);
Sets the currently modifiable matrix to the 4 ´ 4 identity matrix.

If you want to specify explicitly a particular matrix to be loaded as the current matrix, use glLoadMatrix*(). Similarly, use glMultMatrix*() to multiply the current matrix by the matrix passed in as an argument. The argument for both these commands is a vector of sixteen values (m1, m2, ... , m16) that specifies a matrix M as follows:

Remember that you might be able to maximize efficiency by using display lists to store frequently used matrices (and their inverses) rather than recomputing them. (See "Display-List Design Philosophy" in Chapter 7.) (OpenGL implementations often must compute the inverse of the modelview matrix so that normals and clipping planes can be correctly transformed to eye coordinates.)

Caution: If you're programming in C and you declare a matrix as m[4][4], then the element m[i][j] is in the ith column and jth row of the OpenGL transformation matrix. This is the reverse of the standard C convention in which m[i][j] is in row i and column j. To avoid confusion, you should declare your matrices as m[16].

void glLoadMatrix{fd}(const TYPE *m);
Sets the sixteen values of the current matrix to those specified by m.
void glMultMatrix{fd}(const TYPE *m);
Multiplies the matrix specified by the sixteen values pointed to by m by the current matrix and stores the result as the current matrix.

Note: All matrix multiplication with OpenGL occurs as follows: Suppose the current matrix is C and the matrix specified with glMultMatrix*() or any of the transformation commands is M. After multiplication, the final matrix is always CM. Since matrix multiplication isn't generally commutative, the order makes a difference.

Viewing and Modeling Transformations

Viewing and modeling transformations are inextricably related in OpenGL and are in fact combined into a single modelview matrix. (See "A Simple Example: Drawing a Cube.") One of the toughest problems newcomers to computer graphics face is understanding the effects of combined three-dimensional transformations. As you've already seen, there are alternative ways to think about transformations - do you want to move the camera in one direction, or move the object in the opposite direction? Each way of thinking about transformations has advantages and disadvantages, but in some cases one way more naturally matches the effect of the intended transformation. If you can find a natural approach for your particular application, it's easier to visualize the necessary transformations and then write the corresponding code to specify the matrix manipulations. The first part of this section discusses how to think about transformations; later, specific commands are presented. For now, we use only the matrix-manipulation commands you've already seen. Finally, keep in mind that you must call glMatrixMode() with GL_MODELVIEW as its argument prior to performing modeling or viewing transformations.

Thinking about Transformations

Let's start with a simple case of two transformations: a 45-degree counterclockwise rotation about the origin around the z-axis, and a translation down the x-axis. Suppose that the object you're drawing is small compared to the translation (so that you can see the effect of the translation), and that it's originally located at the origin. If you rotate the object first and then translate it, the rotated object appears on the x-axis. If you translate it down the x-axis first, however, and then rotate about the origin, the object is on the line y=x, as shown in Figure 3-4. In general, the order of transformations is critical. If you do transformation A and then transformation B, you almost always get something different than if you do them in the opposite order.

Figure 3-4 : Rotating First or Translating First

Now let's talk about the order in which you specify a series of transformations. All viewing and modeling transformations are represented as 4 ´ 4 matrices. Each successive glMultMatrix*() or transformation command multiplies a new 4 ´ 4 matrix M by the current modelview matrix C to yield CM. Finally, vertices v are multiplied by the current modelview matrix. This process means that the last transformation command called in your program is actually the first one applied to the vertices: CMv. Thus, one way of looking at it is to say that you have to specify the matrices in the reverse order. Like many other things, however, once you've gotten used to thinking about this correctly, backward will seem like forward.

Consider the following code sequence, which draws a single point using three transformations:

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glMultMatrixf(N);                /* apply transformation N */
glMultMatrixf(M);                /* apply transformation M */
glMultMatrixf(L);                /* apply transformation L */
glBegin(GL_POINTS);
glVertex3f(v);                   /* draw transformed vertex v */
glEnd();

With this code, the modelview matrix successively contains I, N, NM, and finally NML, where I represents the identity matrix. The transformed vertex is NMLv. Thus, the vertex transformation is N(M(Lv)) - that is, v is multiplied first by L, the resulting Lv is multiplied by M, and the resulting MLv is multiplied by N. Notice that the transformations to vertex v effectively occur in the opposite order than they were specified. (Actually, only a single multiplication of a vertex by the modelview matrix occurs; in this example, the N, M, and L matrices are already multiplied into a single matrix before it's applied to v.)

Grand, Fixed Coordinate System

Thus, if you like to think in terms of a grand, fixed coordinate system - in which matrix multiplications affect the position, orientation, and scaling of your model - you have to think of the multiplications as occurring in the opposite order from how they appear in the code. Using the simple example shown on the left side of Figure 3-4 (a rotation about the origin and a translation along the x-axis), if you want the object to appear on the axis after the operations, the rotation must occur first, followed by the translation. To do this, you'll need to reverse the order of operations, so the code looks something like this (where R is the rotation matrix and T is the translation matrix):

glMatrixMode(GL_MODELVIEW);

glLoadIdentity();
glMultMatrixf(T);                /* translation */
glMultMatrixf(R);                /* rotation */
draw_the_object();

Moving a Local Coordinate System

Another way to view matrix multiplications is to forget about a grand, fixed coordinate system in which your model is transformed and instead imagine that a local coordinate system is tied to the object you're drawing. All operations occur relative to this changing coordinate system. With this approach, the matrix multiplications now appear in the natural order in the code. (Regardless of which analogy you're using, the code is the same, but how you think about it differs.) To see this in the translation-rotation example, begin by visualizing the object with a coordinate system tied to it. The translation operation moves the object and its coordinate system down the x-axis. Then, the rotation occurs about the (now-translated) origin, so the object rotates in place in its position on the axis.

This approach is what you should use for applications such as articulated robot arms, where there are joints at the shoulder, elbow, and wrist, and on each of the fingers. To figure out where the tips of the fingers go relative to the body, you'd like to start at the shoulder, go down to the wrist, and so on, applying the appropriate rotations and translations at each joint. Thinking about it in reverse would be far more confusing.

This second approach can be problematic, however, in cases where scaling occurs, and especially so when the scaling is nonuniform (scaling different amounts along the different axes). After uniform scaling, translations move a vertex by a multiple of what they did before, since the coordinate system is stretched. Nonuniform scaling mixed with rotations may make the axes of the local coordinate system nonperpendicular.

As mentioned earlier, you normally issue viewing transformation commands in your program before any modeling transformations. This way, a vertex in a model is first transformed into the desired orientation and then transformed by the viewing operation. Since the matrix multiplications must be specified in reverse order, the viewing commands need to come first. Note, however, that you don't need to specify either viewing or modeling transformations if you're satisfied with the default conditions. If there's no viewing transformation, the "camera" is left in the default position at the origin, pointed toward the negative z-axis; if there's no modeling transformation, the model isn't moved, and it retains its specified position, orientation, and size.

Since the commands for performing modeling transformations can be used to perform viewing transformations, modeling transformations are discussed first, even if viewing transformations are actually issued first. This order for discussion also matches the way many programmers think when planning their code: Often, they write all the code necessary to compose the scene, which involves transformations to position and orient objects correctly relative to each other. Next, they decide where they want the viewpoint to be relative to the scene they've composed, and then they write the viewing transformations accordingly.

Modeling Transformations

The three OpenGL routines for modeling transformations are glTranslate*(), glRotate*(), and glScale*(). As you might suspect, these routines transform an object (or coordinate system, if you're thinking of it that way) by moving, rotating, stretching, shrinking, or reflecting it. All three commands are equivalent to producing an appropriate translation, rotation, or scaling matrix, and then calling glMultMatrix*() with that matrix as the argument. However, these three routines might be faster than using glMultMatrix*(). OpenGL automatically computes the matrices for you. (See Appendix F if you're interested in the details.)

In the command summaries that follow, each matrix multiplication is described in terms of what it does to the vertices of a geometric object using the fixed coordinate system approach, and in terms of what it does to the local coordinate system that's attached to an object.

Translate

void glTranslate{fd}(TYPEx, TYPE y, TYPEz);
Multiplies the current matrix by a matrix that moves (translates) an object by the given x, y, and z values (or moves the local coordinate system by the same amounts).

Figure 3-5 shows the effect of glTranslate*().

Figure 3-5 : Translating an Object

Note that using (0.0, 0.0, 0.0) as the argument for glTranslate*() is the identity operation - that is, it has no effect on an object or its local coordinate system.

Rotate

void glRotate{fd}(TYPE angle, TYPE x, TYPE y, TYPE z);
Multiplies the current matrix by a matrix that rotates an object (or the local coordinate system) in a counterclockwise direction about the ray from the origin through the point (x, y, z). The angle parameter specifies the angle of rotation in degrees.

The effect of glRotatef(45.0, 0.0, 0.0, 1.0), which is a rotation of 45 degrees about the z-axis, is shown in Figure 3-6.

Figure 3-6 : Rotating an Object

Note that an object that lies farther from the axis of rotation is more dramatically rotated (has a larger orbit) than an object drawn near the axis. Also, if the angle argument is zero, the glRotate*() command has no effect.

Scale

void glScale{fd}(TYPEx, TYPE y, TYPEz);
Multiplies the current matrix by a matrix that stretches, shrinks, or reflects an object along the axes. Each x, y, and z coordinate of every point in the object is multiplied by the corresponding argument x, y, or z. With the local coordinate system approach, the local coordinate axes are stretched, shrunk, or reflected by the x, y, and z factors, and the associated object is transformed with them.

Figure 3-7 shows the effect of glScalef(2.0, -0.5, 1.0).

Figure 3-7 : Scaling and Reflecting an Object

glScale*() is the only one of the three modeling transformations that changes the apparent size of an object: Scaling with values greater than 1.0 stretches an object, and using values less than 1.0 shrinks it. Scaling with a -1.0 value reflects an object across an axis. The identity values for scaling are (1.0, 1.0, 1.0). In general, you should limit your use of glScale*() to those cases where it is necessary. Using glScale*() decreases the performance of lighting calculations, because the normal vectors have to be renormalized after transformation.

Note: A scale value of zero collapses all object coordinates along that axis to zero. It's usually not a good idea to do this, because such an operation cannot be undone. Mathematically speaking, the matrix cannot be inverted, and inverse matrices are required for certain lighting operations. (See Chapter 5.) Sometimes collapsing coordinates does make sense, however; the calculation of shadows on a planar surface is a typical application. (See "Shadows" in Chapter 14.) In general, if a coordinate system is to be collapsed, the projection matrix should be used rather than the modelview matrix.

A Modeling Transformation Code Example

Example 3-2 is a portion of a program that renders a triangle four times, as shown in Figure 3-8. These are the four transformed triangles.

  • A solid wireframe triangle is drawn with no modeling transformation.
  • The same triangle is drawn again, but with a dashed line stipple and translated (to the left - along the negative x-axis).
  • A triangle is drawn with a long dashed line stipple, with its height (y-axis) halved and its width (x-axis) increased by 50%.
  • A rotated triangle, made of dotted lines, is drawn.

Figure 3-8 : Modeling Transformation Example

Example 3-2 : Using Modeling Transformations: model.c

glLoadIdentity();
glColor3f(1.0, 1.0, 1.0);
draw_triangle();                   /* solid lines */

glEnable(GL_LINE_STIPPLE);         /* dashed lines */
glLineStipple(1, 0xF0F0);
glLoadIdentity();
glTranslatef(-20.0, 0.0, 0.0);
draw_triangle();

glLineStipple(1, 0xF00F);          /*long dashed lines */
glLoadIdentity();
glScalef(1.5, 0.5, 1.0);
draw_triangle();

glLineStipple(1, 0x8888);          /* dotted lines */
glLoadIdentity();
glRotatef (90.0, 0.0, 0.0, 1.0);
draw_triangle ();
glDisable (GL_LINE_STIPPLE);

Note the use of glLoadIdentity() to isolate the effects of modeling transformations; initializing the matrix values prevents successive transformations from having a cumulative effect. Even though using glLoadIdentity() repeatedly has the desired effect, it may be inefficient, because you may have to respecify viewing or modeling transformations. (See "Manipulating the Matrix Stacks" for a better way to isolate transformations.)

Note: Sometimes, programmers who want a continuously rotating object attempt to achieve this by repeatedly applying a rotation matrix that has small values. The problem with this technique is that because of round-off errors, the product of thousands of tiny rotations gradually drifts away from the value you really want (it might even become something that isn't a rotation). Instead of using this technique, increment the angle and issue a new rotation command with the new angle at each update step.

Viewing Transformations

A viewing transformation changes the position and orientation of the viewpoint. If you recall the camera analogy, the viewing transformation positions the camera tripod, pointing the camera toward the model. Just as you move the camera to some position and rotate it until it points in the desired direction, viewing transformations are generally composed of translations and rotations. Also remember that to achieve a certain scene composition in the final image or photograph, you can either move the camera or move all the objects in the opposite direction. Thus, a modeling transformation that rotates an object counterclockwise is equivalent to a viewing transformation that rotates the camera clockwise, for example. Finally, keep in mind that the viewing transformation commands must be called before any modeling transformations are performed, so that the modeling transformations take effect on the objects first.

You can manufacture a viewing transformation in any of several ways, as described next. You can also choose to use the default location and orientation of the viewpoint, which is at the origin, looking down the negative z-axis.

  • Use one or more modeling transformation commands (that is, glTranslate*() and glRotate*()). You can think of the effect of these transformations as moving the camera position or as moving all the objects in the world, relative to a stationary camera.
  • Use the Utility Library routine gluLookAt() to define a line of sight. This routine encapsulates a series of rotation and translation commands.
  • Create your own utility routine that encapsulates rotations and translations. Some applications might require custom routines that allow you to specify the viewing transformation in a convenient way. For example, you might want to specify the roll, pitch, and heading rotation angles of a plane in flight, or you might want to specify a transformation in terms of polar coordinates for a camera that's orbiting around an object.

Using glTranslate*() and glRotate*()

When you use modeling transformation commands to emulate viewing transformations, you're trying to move the viewpoint in a desired way while keeping the objects in the world stationary. Since the viewpoint is initially located at the origin and since objects are often most easily constructed there as well (see Figure 3-9), in general you have to perform some transformation so that the objects can be viewed. Note that, as shown in the figure, the camera initially points down the negative z-axis. (You're seeing the back of the camera.)

Figure 3-9 : Object and Viewpoint at the Origin

In the simplest case, you can move the viewpoint backward, away from the objects; this has the same effect as moving the objects forward, or away from the viewpoint. Remember that by default forward is down the negative z-axis; if you rotate the viewpoint, forward has a different meaning. So, to put 5 units of distance between the viewpoint and the objects by moving the viewpoint, as shown in Figure 3-10, use

glTranslatef(0.0, 0.0, -5.0);

This routine moves the objects in the scene -5 units along the z axis. This is also equivalent to moving the camera +5 units along the z axis.

Figure 3-10 : Separating the Viewpoint and the Object

Now suppose you want to view the objects from the side. Should you issue a rotate command before or after the translate command? If you're thinking in terms of a grand, fixed coordinate system, first imagine both the object and the camera at the origin. You could rotate the object first and then move it away from the camera so that the desired side is visible. Since you know that with the fixed coordinate system approach, commands have to be issued in the opposite order in which they should take effect, you know that you need to write the translate command first in your code and follow it with the rotate command.

Now let's use the local coordinate system approach. In this case, think about moving the object and its local coordinate system away from the origin; then, the rotate command is carried out using the now-translated coordinate system. With this approach, commands are issued in the order in which they're applied, so once again the translate command comes first. Thus, the sequence of transformation commands to produce the desired result is

glTranslatef(0.0, 0.0, -5.0);
glRotatef(90.0, 0.0, 1.0, 0.0);

If you're having trouble keeping track of the effect of successive matrix multiplications, try using both the fixed and local coordinate system approaches and see whether one makes more sense to you. Note that with the fixed coordinate system, rotations always occur about the grand origin, whereas with the local coordinate system, rotations occur about the origin of the local system. You might also try using the gluLookAt() utility routine described in the next section.

Using the gluLookAt() Utility Routine

Often, programmers construct a scene around the origin or some other convenient location, then they want to look at it from an arbitrary point to get a good view of it. As its name suggests, the gluLookAt() utility routine is designed for just this purpose. It takes three sets of arguments, which specify the location of the viewpoint, define a reference point toward which the camera is aimed, and indicate which direction is up. Choose the viewpoint to yield the desired view of the scene. The reference point is typically somewhere in the middle of the scene. (If you've built your scene at the origin, the reference point is probably the origin.) It might be a little trickier to specify the correct up-vector. Again, if you've built some real-world scene at or around the origin and if you've been taking the positive y-axis to point upward, then that's your up-vector for gluLookAt(). However, if you're designing a flight simulator, up is the direction perpendicular to the plane's wings, from the plane toward the sky when the plane is right-side up on the ground.

The gluLookAt() routine is particularly useful when you want to pan across a landscape, for instance. With a viewing volume that's symmetric in both x and y, the (eyex, eyey, eyez) point specified is always in the center of the image on the screen, so you can use a series of commands to move this point slightly, thereby panning across the scene.

void gluLookAt(GLdouble eyex, GLdouble eyey, GLdouble eyez, GLdouble centerx, GLdouble centery, GLdouble centerz, GLdouble upx, GLdouble upy, GLdouble upz);
Defines a viewing matrix and multiplies it to the right of the current matrix. The desired viewpoint is specified by eyex, eyey, and eyez. The centerx, centery, and centerz arguments specify any point along the desired line of sight, but typically they're some point in the center of the scene being looked at. The upx, upy, and upz arguments indicate which direction is up (that is, the direction from the bottom to the top of the viewing volume).

In the default position, the camera is at the origin, is looking down the negative z-axis, and has the positive y-axis as straight up. This is the same as calling

gluLookat (0.0, 0.0, 0.0, 0.0, 0.0, -100.0, 0.0, 1.0, 0.0);

The z value of the reference point is -100.0, but could be any negative z, because the line of sight will remain the same. In this case, you don't actually want to call gluLookAt(), because this is the default (see Figure 3-11) and you are already there! (The lines extending from the camera represent the viewing volume, which indicates its field of view.)

Figure 3-11 : Default Camera Position

Figure 3-12 shows the effect of a typical gluLookAt() routine. The camera position (eyex, eyey, eyez) is at (4, 2, 1). In this case, the camera is looking right at the model, so the reference point is at (2, 4, -3). An orientation vector of (2, 2, -1) is chosen to rotate the viewpoint to this 45-degree angle.

Figure 3-12 : Using gluLookAt()

So, to achieve this effect, call

gluLookAt(4.0, 2.0, 1.0, 2.0, 4.0, -3.0, 2.0, 2.0, -1.0);

Note that gluLookAt() is part of the Utility Library rather than the basic OpenGL library. This isn't because it's not useful, but because it encapsulates several basic OpenGL commands - specifically, glTranslate*() and glRotate*(). To see this, imagine a camera located at an arbitrary viewpoint and oriented according to a line of sight, both as specified with gluLookAt() and a scene located at the origin. To "undo" what gluLookAt() does, you need to transform the camera so that it sits at the origin and points down the negative z-axis, the default position. A simple translate moves the camera to the origin. You can easily imagine a series of rotations about each of the three axes of a fixed coordinate system that would orient the camera so that it pointed toward negative z values. Since OpenGL allows rotation about an arbitrary axis, you can accomplish any desired rotation of the camera with a single glRotate*() command.

Note: You can have only one active viewing transformation. You cannot try to combine the effects of two viewing transformations, any more than a camera can have two tripods. If you want to change the position of the camera, make sure you call glLoadIdentity() to wipe away the effects of any current viewing transformation.

Advanced

To transform any arbitrary vector so that it's coincident with another arbitrary vector (for instance, the negative z-axis), you need to do a little mathematics. The axis about which you want to rotate is given by the cross product of the two normalized vectors. To find the angle of rotation, normalize the initial two vectors. The cosine of the desired angle between the vectors is equal to the dot product of the normalized vectors. The angle of rotation around the axis given by the cross product is always between 0 and 180 degrees. (See Appendix E for definitions of cross and dot products.)

Note that computing the angle between two normalized vectors by taking the inverse cosine of their dot product is not very accurate, especially for small angles. But it should work well enough to get you started.

Creating a Custom Utility Routine

Advanced

For some specialized applications, you might want to define your own transformation routine. Since this is rarely done and in any case is a fairly advanced topic, it's left mostly as an exercise for the reader. The following exercises suggest two custom viewing transformations that might be useful.

Try This

  • Suppose you're writing a flight simulator and you'd like to display the world from the point of view of the pilot of a plane. The world is described in a coordinate system with the origin on the runway and the plane at coordinates (x, y, z). Suppose further that the plane has some roll, pitch, and heading (these are rotation angles of the plane relative to its center of gravity).

Show that the following routine could serve as the viewing transformation:

void pilotView{GLdouble planex, GLdouble planey,
               GLdouble planez, GLdouble roll,
               GLdouble pitch, GLdouble heading)
{
      glRotated(roll, 0.0, 0.0, 1.0);
      glRotated(pitch, 0.0, 1.0, 0.0);
      glRotated(heading, 1.0, 0.0, 0.0);
      glTranslated(-planex, -planey, -planez);
}
  • Suppose your application involves orbiting the camera around an object that's centered at the origin. In this case, you'd like to specify the viewing transformation by using polar coordinates. Let the distance variable define the radius of the orbit, or how far the camera is from the origin. (Initially, the camera is moved distance units along the positive z-axis.) The azimuth describes the angle of rotation of the camera about the object in the x-y plane, measured from the positive y-axis. Similarly, elevation is the angle of rotation of the camera in the y-z plane, measured from the positive z-axis. Finally, twist represents the rotation of the viewing volume around its line of sight.

Show that the following routine could serve as the viewing transformation:

void polarView{GLdouble distance, GLdouble twist,
                 GLdouble elevation, GLdouble azimuth)
{
      glTranslated(0.0, 0.0, -distance);
      glRotated(-twist, 0.0, 0.0, 1.0);
      glRotated(-elevation, 1.0, 0.0, 0.0);
      glRotated(azimuth, 0.0, 0.0, 1.0);
}

Projection Transformations

The previous section described how to compose the desired modelview matrix so that the correct modeling and viewing transformations are applied. This section explains how to define the desired projection matrix, which is also used to transform the vertices in your scene. Before you issue any of the transformation commands described in this section, remember to call

glMatrixMode(GL_PROJECTION);
glLoadIdentity();

so that the commands affect the projection matrix rather than the modelview matrix and so that you avoid compound projection transformations. Since each projection transformation command completely describes a particular transformation, typically you don't want to combine a projection transformation with another transformation.

The purpose of the projection transformation is to define a viewing volume, which is used in two ways. The viewing volume determines how an object is projected onto the screen (that is, by using a perspective or an orthographic projection), and it defines which objects or portions of objects are clipped out of the final image. You can think of the viewpoint we've been talking about as existing at one end of the viewing volume. At this point, you might want to reread "A Simple Example: Drawing a Cube" for its overview of all the transformations, including projection transformations.

Perspective Projection

The most unmistakable characteristic of perspective projection is foreshortening: the farther an object is from the camera, the smaller it appears in the final image. This occurs because the viewing volume for a perspective projection is a frustum of a pyramid (a truncated pyramid whose top has been cut off by a plane parallel to its base). Objects that fall within the viewing volume are projected toward the apex of the pyramid, where the camera or viewpoint is. Objects that are closer to the viewpoint appear larger because they occupy a proportionally larger amount of the viewing volume than those that are farther away, in the larger part of the frustum. This method of projection is commonly used for animation, visual simulation, and any other applications that strive for some degree of realism because it's similar to how our eye (or a camera) works.

The command to define a frustum, glFrustum(), calculates a matrix that accomplishes perspective projection and multiplies the current projection matrix (typically the identity matrix) by it. Recall that the viewing volume is used to clip objects that lie outside of it; the four sides of the frustum, its top, and its base correspond to the six clipping planes of the viewing volume, as shown in Figure 3-13. Objects or parts of objects outside these planes are clipped from the final image. Note that glFrustum() doesn't require you to define a symmetric viewing volume.

Figure 3-13 : Perspective Viewing Volume Specified by glFrustum()

void glFrustum(GLdouble left, GLdouble right, GLdouble bottom,
GLdouble top, GLdouble near, GLdouble far);
Creates a matrix for a perspective-view frustum and multiplies the current matrix by it. The frustum's viewing volume is defined by the parameters: (left, bottom, -near) and (right, top, -near) specify the (x, y, z) coordinates of the lower-left and upper-right corners of the near clipping plane; near and far give the distances from the viewpoint to the near and far clipping planes. They should always be positive.

The frustum has a default orientation in three-dimensional space. You can perform rotations or translations on the projection matrix to alter this orientation, but this is tricky and nearly always avoidable.

Advanced

Also, the frustum doesn't have to be symmetrical, and its axis isn't necessarily aligned with the z-axis. For example, you can use glFrustum() to draw a picture as if you were looking through a rectangular window of a house, where the window was above and to the right of you. Photographers use such a viewing volume to create false perspectives. You might use it to have the hardware calculate images at much higher than normal resolutions, perhaps for use on a printer. For example, if you want an image that has twice the resolution of your screen, draw the same picture four times, each time using the frustum to cover the entire screen with one-quarter of the image. After each quarter of the image is rendered, you can read the pixels back to collect the data for the higher-resolution image. (See Chapter 8 for more information about reading pixel data.)

Although it's easy to understand conceptually, glFrustum() isn't intuitive to use. Instead, you might try the Utility Library routine gluPerspective(). This routine creates a viewing volume of the same shape as glFrustum() does, but you specify it in a different way. Rather than specifying corners of the near clipping plane, you specify the angle of the field of view ( &THgr; , or theta, in Figure 3-14) in the y direction and the aspect ratio of the width to height (x/y). (For a square portion of the screen, the aspect ratio is 1.0.) These two parameters are enough to determine an untruncated pyramid along the line of sight, as shown in Figure 3-14. You also specify the distance between the viewpoint and the near and far clipping planes, thereby truncating the pyramid. Note that gluPerspective() is limited to creating frustums that are symmetric in both the x- and y-axes along the line of sight, but this is usually what you want.

Figure 3-14 : Perspective Viewing Volume Specified by gluPerspective()

void gluPerspective(GLdouble fovy, GLdouble aspect,
GLdouble near, GLdouble far);
Creates a matrix for a symmetric perspective-view frustum and multiplies the current matrix by it. fovy is the angle of the field of view in the x-z plane; its value must be in the range [0.0,180.0]. aspect is the aspect ratio of the frustum, its width divided by its height. near and far values the distances between the viewpoint and the clipping planes, along the negative z-axis. They should always be positive.

Just as with glFrustum(), you can apply rotations or translations to change the default orientation of the viewing volume created by gluPerspective(). With no such transformations, the viewpoint remains at the origin, and the line of sight points down the negative z-axis.

With gluPerspective(), you need to pick appropriate values for the field of view, or the image may look distorted. For example, suppose you're drawing to the entire screen, which happens to be 11 inches high. If you choose a field of view of 90 degrees, your eye has to be about 7.8 inches from the screen for the image to appear undistorted. (This is the distance that makes the screen subtend 90 degrees.) If your eye is farther from the screen, as it usually is, the perspective doesn't look right. If your drawing area occupies less than the full screen, your eye has to be even closer. To get a perfect field of view, figure out how far your eye normally is from the screen and how big the window is, and calculate the angle the window subtends at that size and distance. It's probably smaller than you would guess. Another way to think about it is that a 94-degree field of view with a 35-millimeter camera requires a 20-millimeter lens, which is a very wide-angle lens. (See "Troubleshooting Transformations" for more details on how to calculate the desired field of view.)

The preceding paragraph mentions inches and millimeters - do these really have anything to do with OpenGL? The answer is, in a word, no. The projection and other transformations are inherently unitless. If you want to think of the near and far clipping planes as located at 1.0 and 20.0 meters, inches, kilometers, or leagues, it's up to you. The only rule is that you have to use a consistent unit of measurement. Then the resulting image is drawn to scale.

Orthographic Projection

With an orthographic projection, the viewing volume is a rectangular parallelepiped, or more informally, a box (see Figure 3-15). Unlike perspective projection, the size of the viewing volume doesn't change from one end to the other, so distance from the camera doesn't affect how large an object appears. This type of projection is used for applications such as creating architectural blueprints and computer-aided design, where it's crucial to maintain the actual sizes of objects and angles between them as they're projected.

Figure 3-15 : Orthographic Viewing Volume

The command glOrtho() creates an orthographic parallel viewing volume. As with glFrustum(), you specify the corners of the near clipping plane and the distance to the far clipping plane.

void glOrtho(GLdouble left, GLdouble right, GLdouble bottom,
GLdouble top, GLdouble near, GLdouble far);
Creates a matrix for an orthographic parallel viewing volume and multiplies the current matrix by it. (left, bottom, -near) and (right, top, -near) are points on the near clipping plane that are mapped to the lower-left and upper-right corners of the viewport window, respectively. (left, bottom, -far) and (right, top, -far) are points on the far clipping plane that are mapped to the same respective corners of the viewport. Both near and far can be positive or negative.

With no other transformations, the direction of projection is parallel to the z-axis, and the viewpoint faces toward the negative z-axis. Note that this means that the values passed in for far and near are used as negative z values if these planes are in front of the viewpoint, and positive if they're behind the viewpoint.

For the special case of projecting a two-dimensional image onto a two-dimensional screen, use the Utility Library routine gluOrtho2D(). This routine is identical to the three-dimensional version, glOrtho(), except that all the z coordinates for objects in the scene are assumed to lie between -1.0 and 1.0. If you're drawing two-dimensional objects using the two-dimensional vertex commands, all the z coordinates are zero; thus, none of the objects are clipped because of their z values.

void gluOrtho2D(GLdouble left, GLdouble right,
GLdouble bottom, GLdouble top);
Creates a matrix for projecting two-dimensional coordinates onto the screen and multiplies the current projection matrix by it. The clipping region is a rectangle with the lower-left corner at (left, bottom) and the upper-right corner at (right, top).

Viewing Volume Clipping

After the vertices of the objects in the scene have been transformed by the modelview and projection matrices, any primitives that lie outside the viewing volume are clipped. The six clipping planes used are those that define the sides and ends of the viewing volume. You can specify additional clipping planes and locate them wherever you choose. (See "Additional Clipping Planes" for information about this relatively advanced topic.) Keep in mind that OpenGL reconstructs the edges of polygons that get clipped.

Viewport Transformation

Recalling the camera analogy, you know that the viewport transformation corresponds to the stage where the size of the developed photograph is chosen. Do you want a wallet-size or a poster-size photograph? Since this is computer graphics, the viewport is the rectangular region of the window where the image is drawn. Figure 3-16 shows a viewport that occupies most of the screen. The viewport is measured in window coordinates, which reflect the position of pixels on the screen relative to the lower-left corner of the window. Keep in mind that all vertices have been transformed by the modelview and projection matrices by this point, and vertices outside the viewing volume have been clipped.

Figure 3-16 : Viewport Rectangle

Defining the Viewport

The window system, not OpenGL, is responsible for opening a window on the screen. However, by default the viewport is set to the entire pixel rectangle of the window that's opened. You use the glViewport() command to choose a smaller drawing region; for example, you can subdivide the window to create a split-screen effect for multiple views in the same window.

void glViewport(GLint x, GLint y, GLsizei width, GLsizei height);
Defines a pixel rectangle in the window into which the final image is mapped. The (x, y) parameter specifies the lower-left corner of the viewport, and width and height are the size of the viewport rectangle. By default, the initial viewport values are (0, 0, winWidth, winHeight), where winWidth and winHeight are the size of the window.

The aspect ratio of a viewport should generally equal the aspect ratio of the viewing volume. If the two ratios are different, the projected image will be distorted when mapped to the viewport, as shown in Figure 3-17. Note that subsequent changes to the size of the window don't explicitly affect the viewport. Your application should detect window resize events and modify the viewport appropriately.

Figure 3-17 : Mapping the Viewing Volume to the Viewport

In Figure 3-17, the left figure shows a projection that maps a square image onto a square viewport using these routines:

gluPerspective(fovy, 1.0, near, far);
glViewport(0, 0, 400, 400);

However, in the right figure, the window has been resized to a nonequilateral rectangular viewport, but the projection is unchanged. The image appears compressed along the x-axis.

gluPerspective(fovy, 1.0, near, far);
glViewport (0, 0, 400, 200);

To avoid the distortion, modify the aspect ratio of the projection to match the viewport:

gluPerspective(fovy, 2.0, near, far);
glViewport(0, 0, 400, 200);

Try This

Modify an existing program so that an object is drawn twice, in different viewports. You might draw the object with different projection and/or viewing transformations for each viewport. To create two side-by-side viewports, you might issue these commands, along with the appropriate modeling, viewing, and projection transformations:

glViewport (0, 0, sizex/2, sizey);
                    .
                    .
                    .
glViewport (sizex/2, 0, sizex/2, sizey);

The Transformed Depth Coordinate

The depth (z) coordinate is encoded during the viewport transformation (and later stored in the depth buffer). You can scale z values to lie within a desired range with the glDepthRange() command. (Chapter 10 discusses the depth buffer and the corresponding uses for the depth coordinate.) Unlike x and y window coordinates, z window coordinates are treated by OpenGL as though they always range from 0.0 to 1.0.

void glDepthRange(GLclampd near, GLclampd far);
Defines an encoding for z coordinates that's performed during the viewport transformation. The near and far values represent adjustments to the minimum and maximum values that can be stored in the depth buffer. By default, they're 0.0 and 1.0, respectively, which work for most applications. These parameters are clamped to lie within [0,1].

In perspective projection, the transformed depth coordinate (like the x and y coordinates) is subject to perspective division by the w coordinate. As the transformed depth coordinate moves farther away from the near clipping plane, its location becomes increasingly less precise. (See Figure 3-18.)

Figure 3-18 : Perspective Projection and Transformed Depth Coordinates

Therefore, perspective division affects the accuracy of operations which rely upon the transformed depth coordinate, especially depth-buffering, which is used for hidden surface removal.

Troubleshooting Transformations

It's pretty easy to get a camera pointed in the right direction, but in computer graphics, you have to specify position and direction with coordinates and angles. As we can attest, it's all too easy to achieve the well-known black-screen effect. Although any number of things can go wrong, often you get this effect - which results in absolutely nothing being drawn in the window you open on the screen - from incorrectly aiming the "camera" and taking a picture with the model behind you. A similar problem arises if you don't choose a field of view that's wide enough to view your objects but narrow enough so they appear reasonably large.

If you find yourself exerting great programming effort only to create a black window, try these diagnostic steps.

Check the obvious possibilities. Make sure your system is plugged in. Make sure you're drawing your objects with a color that's different from the color with which you're clearing the screen. Make sure that whatever states you're using (such as lighting, texturing, alpha blending, logical operations, or antialiasing) are correctly turned on or off, as desired.

Remember that with the projection commands, the near and far coordinates measure distance from the viewpoint and that (by default) you're looking down the negative z axis. Thus, if the near value is 1.0 and the far 3.0, objects must have z coordinates between -1.0 and -3.0 in order to be visible. To ensure that you haven't clipped everything out of your scene, temporarily set the near and far clipping planes to some absurdly inclusive values, such as 0.001 and 1000000.0. This alters appearance for operations such as depth-buffering and fog, but it might uncover inadvertently clipped objects.

Determine where the viewpoint is, in which direction you're looking, and where your objects are. It might help to create a real three-dimensional space - using your hands, for instance - to figure these things out.

Make sure you know where you're rotating about. You might be rotating about some arbitrary location unless you translated back to the origin first. It's OK to rotate about any point unless you're expecting to rotate about the origin.

Check your aim. Use gluLookAt() to aim the viewing volume at your objects. Or draw your objects at or near the origin, and use glTranslate*() as a viewing transformation to move the camera far enough in the z direction only so that the objects fall within the viewing volume. Once you've managed to make your objects visible, try to change the viewing volume incrementally to achieve the exact result you want, as described next.

Even after you've aimed the camera in the correct direction and you can see your objects, they might appear too small or too large. If you're using gluPerspective(), you might need to alter the angle defining the field of view by changing the value of the first parameter for this command. You can use trigonometry to calculate the desired field of view given the size of the object and its distance from the viewpoint: The tangent of half the desired angle is half the size of the object divided by the distance to the object (see Figure 3-19). Thus, you can use an arctangent routine to compute half the desired angle. Example 3-3 assumes such a routine, atan2(), which calculates the arctangent given the length of the opposite and adjacent sides of a right triangle. This result then needs to be converted from radians to degrees.

Figure 3-19 : Using Trigonometry to Calculate the Field of View

Example 3-3 : Calculating Field of View

#define PI 3.1415926535

double calculateAngle(double size, double distance)
{
    double radtheta, degtheta;

    radtheta = 2.0 * atan2 (size/2.0, distance);
    degtheta = (180.0 * radtheta) / PI;
    return (degtheta);
}

Of course, typically you don't know the exact size of an object, and the distance can only be determined between the viewpoint and a single point in your scene. To obtain a fairly good approximate value, find the bounding box for your scene by determining the maximum and minimum x, y, and z coordinates of all the objects in your scene. Then calculate the radius of a bounding sphere for that box, and use the center of the sphere to determine the distance and the radius to determine the size.

For example, suppose all the coordinates in your object satisfy the equations -1 £ x ³ 3, 5 £ y ³ 7, and -5 £ z ³ 5. Then the center of the bounding box is (1, 6, 0), and the radius of a bounding sphere is the distance from the center of the box to any corner - say (3, 7, 5) - or

If the viewpoint is at (8, 9, 10), the distance between it and the center is

The tangent of the half angle is 5.477 divided by 12.570, which equals 0.4357, so the half angle is 23.54 degrees.

Remember that the field-of-view angle affects the optimal position for the viewpoint, if you're trying to achieve a realistic image. For example, if your calculations indicate that you need a 179-degree field of view, the viewpoint must be a fraction of an inch from the screen to achieve realism. If your calculated field of view is too large, you might need to move the viewpoint farther away from the object.

Manipulating the Matrix Stacks

The modelview and projection matrices you've been creating, loading, and multiplying have only been the visible tips of their respective icebergs. Each of these matrices is actually the topmost member of a stack of matrices (see Figure 3-20).

Figure 3-20 : Modelview and Projection Matrix Stacks

A stack of matrices is useful for constructing hierarchical models, in which complicated objects are constructed from simpler ones. For example, suppose you're drawing an automobile that has four wheels, each of which is attached to the car with five bolts. You have a single routine to draw a wheel and another to draw a bolt, since all the wheels and all the bolts look the same. These routines draw a wheel or a bolt in some convenient position and orientation, say centered at the origin with its axis coincident with the z axis. When you draw the car, including the wheels and bolts, you want to call the wheel-drawing routine four times with different transformations in effect each time to position the wheels correctly. As you draw each wheel, you want to draw the bolts five times, each time translated appropriately relative to the wheel.

Suppose for a minute that all you have to do is draw the car body and the wheels. The English description of what you want to do might be something like this:

  • Draw the car body. Remember where you are, and translate to the right front wheel. Draw the wheel and throw away the last translation so your current position is back at the origin of the car body. Remember where you are, and translate to the left front wheel....

Similarly, for each wheel, you want to draw the wheel, remember where you are, and successively translate to each of the positions that bolts are drawn, throwing away the transformations after each bolt is drawn.

Since the transformations are stored as matrices, a matrix stack provides an ideal mechanism for doing this sort of successive remembering, translating, and throwing away. All the matrix operations that have been described so far (glLoadMatrix(), glMultMatrix(), glLoadIdentity() and the commands that create specific transformation matrices) deal with the current matrix, or the top matrix on the stack. You can control which matrix is on top with the commands that perform stack operations: glPushMatrix(), which copies the current matrix and adds the copy to the top of the stack, and glPopMatrix(), which discards the top matrix on the stack, as shown in Figure 3-21. (Remember that the current matrix is always the matrix on the top.) In effect, glPushMatrix() means "remember where you are" and glPopMatrix() means "go back to where you were."

Figure 3-21 : Pushing and Popping the Matrix Stack

void glPushMatrix(void);
Pushes all matrices in the current stack down one level. The current stack is determined by glMatrixMode(). The topmost matrix is copied, so its contents are duplicated in both the top and second-from-the-top matrix. If too many matrices are pushed, an error is generated.
void glPopMatrix(void);
Pops the top matrix off the stack, destroying the contents of the popped matrix. What was the second-from-the-top matrix becomes the top matrix. The current stack is determined by glMatrixMode(). If the stack contains a single matrix, calling glPopMatrix() generates an error.

Example 3-4 draws an automobile, assuming the existence of routines that draw the car body, a wheel, and a bolt.

Example 3-4 : Pushing and Popping the Matrix

draw_wheel_and_bolts()
{
   long i;

   draw_wheel();
   for(i=0;i<5;i++){
      glPushMatrix();
         glRotatef(72.0*i,0.0,0.0,1.0);
         glTranslatef(3.0,0.0,0.0);
         draw_bolt();
      glPopMatrix();
   }
}

draw_body_and_wheel_and_bolts()
{
   draw_car_body();
   glPushMatrix();
      glTranslatef(40,0,30);    /*move to first wheel position*/
      draw_wheel_and_bolts();
   glPopMatrix();
   glPushMatrix();
      glTranslatef(40,0,-30);   /*move to 2nd wheel position*/
      draw_wheel_and_bolts();
   glPopMatrix();
   ...                    /*draw last two wheels similarly*/
}

This code assumes the wheel and bolt axes are coincident with the z-axis, that the bolts are evenly spaced every 72 degrees, 3 units (maybe inches) from the center of the wheel, and that the front wheels are 40 units in front of and 30 units to the right and left of the car's origin.

A stack is more efficient than an individual matrix, especially if the stack is implemented in hardware. When you push a matrix, you don't need to copy the current data back to the main process, and the hardware may be able to copy more than one element of the matrix at a time. Sometimes you might want to keep an identity matrix at the bottom of the stack so that you don't need to call glLoadIdentity() repeatedly.

The Modelview Matrix Stack

As you've seen earlier in "Viewing and Modeling Transformations," the modelview matrix contains the cumulative product of multiplying viewing and modeling transformation matrices. Each viewing or modeling transformation creates a new matrix that multiplies the current modelview matrix; the result, which becomes the new current matrix, represents the composite transformation. The modelview matrix stack contains at least thirty-two 4 ´ 4 matrices; initially, the topmost matrix is the identity matrix. Some implementations of OpenGL may support more than thirty-two matrices on the stack. To find the maximum allowable number of matrices, you can use the query command glGetIntegerv(GL_MAX_MODELVIEW_STACK_DEPTH, GLint *params).

The Projection Matrix Stack

The projection matrix contains a matrix for the projection transformation, which describes the viewing volume. Generally, you don't want to compose projection matrices, so you issue glLoadIdentity() before performing a projection transformation. Also for this reason, the projection matrix stack need be only two levels deep; some OpenGL implementations may allow more than two 4 ´ 4 matrices. To find the stack depth, call glGetIntegerv(GL_MAX_PROJECTION_STACK_DEPTH, GLint *params).

One use for a second matrix in the stack would be an application that needs to display a help window with text in it, in addition to its normal window showing a three-dimensional scene. Since text is most easily positioned with an orthographic projection, you could change temporarily to an orthographic projection, display the help, and then return to your previous projection:

glMatrixMode(GL_PROJECTION);
glPushMatrix();                  /*save the current projection*/
    glLoadIdentity();
    glOrtho(...);                /*set up for displaying help*/
    display_the_help();
glPopMatrix();

Note that you'd probably have to also change the modelview matrix appropriately.

Advanced

If you know enough mathematics, you can create custom projection matrices that perform arbitrary projective transformations. For example, the OpenGL and its Utility Library have no built-in mechanism for two-point perspective. If you were trying to emulate the drawings in drafting texts, you might need such a projection matrix.

Additional Clipping Planes

In addition to the six clipping planes of the viewing volume (left, right, bottom, top, near, and far), you can define up to six additional clipping planes to further restrict the viewing volume, as shown in Figure 3-22. This is useful for removing extraneous objects in a scene - for example, if you want to display a cutaway view of an object.

Each plane is specified by the coefficients of its equation: Ax+By+Cz+D = 0. The clipping planes are automatically transformed appropriately by modeling and viewing transformations. The clipping volume becomes the intersection of the viewing volume and all half-spaces defined by the additional clipping planes. Remember that polygons that get clipped automatically have their edges reconstructed appropriately by OpenGL.

Figure 3-22 : Additional Clipping Planes and the Viewing Volume

void glClipPlane(GLenum plane, const GLdouble *equation);
Defines a clipping plane. The equation argument points to the four coefficients of the plane equation, A x+B y+C z+D = 0. All points with eye coordinates (xe, ye, ze, we) that satisfy (A B C D)M-1 (xe ye ze we)T >= 0 lie in the half-space defined by the plane, where M is the current modelview matrix at the time glClipPlane() is called. All points not in this half-space are clipped away. The plane argument is GL_CLIP_PLANE i, where i is an integer specifying which of the available clipping planes to define. i is a number between 0 and one less than the maximum number of additional clipping planes.

You need to enable each additional clipping plane you define:

glEnable(GL_CLIP_PLANEi);

You can disable a plane with

glDisable(GL_CLIP_PLANEi);

All implementations of OpenGL must support at least six additional clipping planes, although some implementations may allow more. You can use glGetIntegerv() with GL_MAX_CLIP_PLANES to find how many clipping planes are supported.

Note: Clipping performed as a result of glClipPlane() is done in eye coordinates, not in clip coordinates. This difference is noticeable if the projection matrix is singular (that is, a real projection matrix that flattens three-dimensional coordinates to two-dimensional ones). Clipping performed in eye coordinates continues to take place in three dimensions even when the projection matrix is singular.

A Clipping Plane Code Example

Example 3-5 renders a wireframe sphere with two clipping planes that slice away three-quarters of the original sphere, as shown in Figure 3-23.

Figure 3-23 : Clipped Wireframe Sphere

Example 3-5 : Wireframe Sphere with Two Clipping Planes: clip.c

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>

void init(void)
{
   glClearColor (0.0, 0.0, 0.0, 0.0);
   glShadeModel (GL_FLAT);
}

void display(void)
{
   GLdouble eqn[4] = {0.0, 1.0, 0.0, 0.0};
   GLdouble eqn2[4] = {1.0, 0.0, 0.0, 0.0};

   glClear(GL_COLOR_BUFFER_BIT);
   glColor3f (1.0, 1.0, 1.0);
   glPushMatrix();
   glTranslatef (0.0, 0.0, -5.0);

/*    clip lower half -- y < 0          */
   glClipPlane (GL_CLIP_PLANE0, eqn);
   glEnable (GL_CLIP_PLANE0);
/*    clip left half -- x < 0           */
   glClipPlane (GL_CLIP_PLANE1, eqn2);
   glEnable (GL_CLIP_PLANE1);

   glRotatef (90.0, 1.0, 0.0, 0.0);
   glutWireSphere(1.0, 20, 16);
   glPopMatrix();
   glFlush ();
}

void reshape (int w, int h)
{
   glViewport (0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode (GL_PROJECTION);
   glLoadIdentity ();
   gluPerspective(60.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0);
   glMatrixMode (GL_MODELVIEW);
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
   glutInitWindowSize (500, 500);
   glutInitWindowPosition (100, 100);
   glutCreateWindow (argv[0]);
   init ();
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutMainLoop();
   return 0;
}

Try This

  • Try changing the coefficients that describe the clipping planes in Example 3-5.
  • Try calling a modeling transformation, such as glRotate*(), to affect glClipPlane(). Make the clipping plane move independently of the objects in the scene.

Examples of Composing Several Transformations

This section demonstrates how to combine several transformations to achieve a particular result. The two examples discussed are a solar system, in which objects need to rotate on their axes as well as in orbit around each other, and a robot arm, which has several joints that effectively transform coordinate systems as they move relative to each other.

Building a Solar System

The program described in this section draws a simple solar system with a planet and a sun, both using the same sphere-drawing routine. To write this program, you need to use glRotate*() for the revolution of the planet around the sun and for the rotation of the planet around its own axis. You also need glTranslate*() to move the planet out to its orbit, away from the origin of the solar system. Remember that you can specify the desired size of the two spheres by supplying the appropriate arguments for the glutWireSphere() routine.

To draw the solar system, you first want to set up a projection and a viewing transformation. For this example, gluPerspective() and gluLookAt() are used.

Drawing the sun is straightforward, since it should be located at the origin of the grand, fixed coordinate system, which is where the sphere routine places it. Thus, drawing the sun doesn't require translation; you can use glRotate*() to make the sun rotate about an arbitrary axis. To draw a planet rotating around the sun, as shown in Figure 3-24, requires several modeling transformations. The planet needs to rotate about its own axis once a day. And once a year, the planet completes one revolution around the sun.

Figure 3-24 : Planet and Sun

To determine the order of modeling transformations, visualize what happens to the local coordinate system. An initial glRotate*() rotates the local coordinate system that initially coincides with the grand coordinate system. Next, glTranslate*() moves the local coordinate system to a position on the planet's orbit; the distance moved should equal the radius of the orbit. Thus, the initial glRotate*() actually determines where along the orbit the planet is (or what time of year it is).

A second glRotate*() rotates the local coordinate system around the local axes, thus determining the time of day for the planet. Once you've issued all these transformation commands, the planet can be drawn.

In summary, these are the OpenGL commands to draw the sun and planet; the full program is shown in Example 3-6.

glPushMatrix();
glutWireSphere(1.0, 20, 16);     /* draw sun */
glRotatef ((GLfloat) year, 0.0, 1.0, 0.0);
glTranslatef (2.0, 0.0, 0.0);
glRotatef ((GLfloat) day, 0.0, 1.0, 0.0);
glutWireSphere(0.2, 10, 8);      /* draw smaller planet */
glPopMatrix();

Example 3-6 : Planetary System: planet.c

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>

static int year = 0, day = 0;

void init(void)
{
   glClearColor (0.0, 0.0, 0.0, 0.0);
   glShadeModel (GL_FLAT);
}

void display(void)
{
   glClear (GL_COLOR_BUFFER_BIT);
   glColor3f (1.0, 1.0, 1.0);

   glPushMatrix();
   glutWireSphere(1.0, 20, 16);   /* draw sun */
   glRotatef ((GLfloat) year, 0.0, 1.0, 0.0);
   glTranslatef (2.0, 0.0, 0.0);
   glRotatef ((GLfloat) day, 0.0, 1.0, 0.0);
   glutWireSphere(0.2, 10, 8);    /* draw smaller planet */
   glPopMatrix();
   glutSwapBuffers();
}

void reshape (int w, int h)
{
   glViewport (0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode (GL_PROJECTION);
   glLoadIdentity ();
   gluPerspective(60.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
   gluLookAt (0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
}

void keyboard (unsigned char key, int x, int y)
{
   switch (key) {
      case `d':
         day = (day + 10) % 360;
         glutPostRedisplay();
         break;
      case `D':
         day = (day - 10) % 360;
         glutPostRedisplay();
         break;
      case `y':
         year = (year + 5) % 360;
         glutPostRedisplay();
         break;
      case `Y':
         year = (year - 5) % 360;
         glutPostRedisplay();
         break;
      default:
         break;
   }
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB);
   glutInitWindowSize (500, 500);
   glutInitWindowPosition (100, 100);
   glutCreateWindow (argv[0]);
   init ();
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyboard);
   glutMainLoop();
   return 0;
}

Try This

  • Try adding a moon to the planet. Or try several moons and additional planets. Hint: Use glPushMatrix() and glPopMatrix() to save and restore the position and orientation of the coordinate system at appropriate moments. If you're going to draw several moons around a planet, you need to save the coordinate system prior to positioning each moon and restore the coordinate system after each moon is drawn.
  • Try tilting the planet's axis.

Building an Articulated Robot Arm

This section discusses a program that creates an articulated robot arm with two or more segments. The arm should be connected with pivot points at the shoulder, elbow, or other joints. Figure 3-25 shows a single joint of such an arm.

Figure 3-25 : Robot Arm

You can use a scaled cube as a segment of the robot arm, but first you must call the appropriate modeling transformations to orient each segment. Since the origin of the local coordinate system is initially at the center of the cube, you need to move the local coordinate system to one edge of the cube. Otherwise, the cube rotates about its center rather than the pivot point.

After you call glTranslate*() to establish the pivot point and glRotate*() to pivot the cube, translate back to the center of the cube. Then the cube is scaled (flattened and widened) before it is drawn. The glPushMatrix() and glPopMatrix() restrict the effect of glScale*(). Here's what your code might look like for this first segment of the arm (the entire program is shown in Example 3-7):

glTranslatef (-1.0, 0.0, 0.0);
glRotatef ((GLfloat) shoulder, 0.0, 0.0, 1.0);
glTranslatef (1.0, 0.0, 0.0);
glPushMatrix();
glScalef (2.0, 0.4, 1.0);
glutWireCube (1.0);
glPopMatrix();

To build a second segment, you need to move the local coordinate system to the next pivot point. Since the coordinate system has previously been rotated, the x-axis is already oriented along the length of the rotated arm. Therefore, translating along the x-axis moves the local coordinate system to the next pivot point. Once it's at that pivot point, you can use the same code to draw the second segment as you used for the first one. This can be continued for an indefinite number of segments (shoulder, elbow, wrist, fingers).

glTranslatef (1.0, 0.0, 0.0);
glRotatef ((GLfloat) elbow, 0.0, 0.0, 1.0);
glTranslatef (1.0, 0.0, 0.0);
glPushMatrix();
glScalef (2.0, 0.4, 1.0);
glutWireCube (1.0);
glPopMatrix();

Example 3-7 : Robot Arm: robot.c

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>

static int shoulder = 0, elbow = 0;

void init(void)
{
  glClearColor (0.0, 0.0, 0.0, 0.0);
  glShadeModel (GL_FLAT);
}

void display(void)
{
   glClear (GL_COLOR_BUFFER_BIT);
   glPushMatrix();
   glTranslatef (-1.0, 0.0, 0.0);
   glRotatef ((GLfloat) shoulder, 0.0, 0.0, 1.0);
   glTranslatef (1.0, 0.0, 0.0);
   glPushMatrix();
   glScalef (2.0, 0.4, 1.0);
   glutWireCube (1.0);
   glPopMatrix();

   glTranslatef (1.0, 0.0, 0.0);
   glRotatef ((GLfloat) elbow, 0.0, 0.0, 1.0);
   glTranslatef (1.0, 0.0, 0.0);
   glPushMatrix();
   glScalef (2.0, 0.4, 1.0);
   glutWireCube (1.0);
   glPopMatrix();

   glPopMatrix();
   glutSwapBuffers();
}

void reshape (int w, int h)
{
   glViewport (0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode (GL_PROJECTION);
   glLoadIdentity ();
   gluPerspective(65.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
   glTranslatef (0.0, 0.0, -5.0);
}

void keyboard (unsigned char key, int x, int y)
{
   switch (key) {
      case `s':   /*  s key rotates at shoulder  */
         shoulder = (shoulder + 5) % 360;
         glutPostRedisplay();
         break;
      case `S':
         shoulder = (shoulder - 5) % 360;
         glutPostRedisplay();
         break;
      case `e':  /*  e key rotates at elbow  */
         elbow = (elbow + 5) % 360;
         glutPostRedisplay();
         break;
      case `E':
         elbow = (elbow - 5) % 360;
         glutPostRedisplay();
         break;
      default:
         break;
   }
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB);
   glutInitWindowSize (500, 500);
   glutInitWindowPosition (100, 100);
   glutCreateWindow (argv[0]);
   init ();
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyboard);
   glutMainLoop();
   return 0;
}

Try This

  • Modify Example 3-7 to add additional segments onto the robot arm.
  • Modify Example 3-7 to add additional segments at the same position. For example, give the robot arm several "fingers" at the wrist, as shown in Figure 3-26. Hint: Use glPushMatrix() and glPopMatrix() to save and restore the position and orientation of the coordinate system at the wrist. If you're going to draw fingers at the wrist, you need to save the current matrix prior to positioning each finger and restore the current matrix after each finger is drawn.

Figure 3-26 : Robot Arm with Fingers

Reversing or Mimicking Transformations

The geometric processing pipeline is very good at using viewing and projection matrices and a viewport for clipping to transform the world (or object) coordinates of a vertex into window (or screen) coordinates. However, there are situations in which you want to reverse that process. A common situation is when an application user utilizes the mouse to choose a location in three dimensions. The mouse returns only a two-dimensional value, which is the screen location of the cursor. Therefore, the application will have to reverse the transformation process to determine from where in three-dimensional space this screen location originated.

The Utility Library routine gluUnProject() performs this reversal of the transformations. Given the three-dimensional window coordinates for a location and all the transformations that affected them, gluUnProject() returns the world coordinates from where it originated.

int gluUnProject(GLdouble winx, GLdouble winy, GLdouble winz, const GLdouble modelMatrix[16], const GLdouble projMatrix[16], const GLint viewport[4], GLdouble *objx, GLdouble *objy, GLdouble *objz);
Map the specified window coordinates (winx, winy, winz) into object coordinates, using transformations defined by a modelview matrix (modelMatrix), projection matrix (projMatrix), and viewport (viewport). The resulting object coordinates are returned in objx, objy, and objz. The function returns GL_TRUE, indicating success, or GL_FALSE, indicating failure (such as an noninvertible matrix). This operation does not attempt to clip the coordinates to the viewport or eliminate depth values that fall outside of glDepthRange().

There are inherent difficulties in trying to reverse the transformation process. A two-dimensional screen location could have originated from anywhere on an entire line in three-dimensional space. To disambiguate the result, gluUnProject() requires that a window depth coordinate (winz) be provided and that winz be specified in terms of glDepthRange(). For the default values of glDepthRange(), winz at 0.0 will request the world coordinates of the transformed point at the near clipping plane, while winz at 1.0 will request the point at the far clipping plane.

Example 3-8 demonstrates gluUnProject() by reading the mouse position and determining the three-dimensional points at the near and far clipping planes from which it was transformed. The computed world coordinates are printed to standard output, but the rendered window itself is just black.

Example 3-8 : Reversing the Geometric Processing Pipeline: unproject.c

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <stdlib.h>
#include <stdio.h>

void display(void)
{
   glClear(GL_COLOR_BUFFER_BIT);
   glFlush();
}

void reshape(int w, int h)
{
   glViewport (0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluPerspective (45.0, (GLfloat) w/(GLfloat) h, 1.0, 100.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
}

void mouse(int button, int state, int x, int y)
{
   GLint viewport[4];
   GLdouble mvmatrix[16], projmatrix[16];
   GLint realy;  /*  OpenGL y coordinate position  */
   GLdouble wx, wy, wz;  /*  returned world x, y, z coords  */

   switch (button) {
      case GLUT_LEFT_BUTTON:
         if (state == GLUT_DOWN) {
            glGetIntegerv (GL_VIEWPORT, viewport);
            glGetDoublev (GL_MODELVIEW_MATRIX, mvmatrix);
            glGetDoublev (GL_PROJECTION_MATRIX, projmatrix);
/*  note viewport[3] is height of window in pixels  */
            realy = viewport[3] - (GLint) y - 1;
            printf ("Coordinates at cursor are (%4d, %4d)\n",
               x, realy);
            gluUnProject ((GLdouble) x, (GLdouble) realy, 0.0,
               mvmatrix, projmatrix, viewport, &wx, &wy, &wz);
            printf ("World coords at z=0.0 are (%f, %f, %f)\n",
               wx, wy, wz);
            gluUnProject ((GLdouble) x, (GLdouble) realy, 1.0,
               mvmatrix, projmatrix, viewport, &wx, &wy, &wz);
            printf ("World coords at z=1.0 are (%f, %f, %f)\n",
               wx, wy, wz);
         }
         break;
      case GLUT_RIGHT_BUTTON:
         if (state == GLUT_DOWN)
            exit(0);
         break;
      default:
         break;
   }
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
   glutInitWindowSize (500, 500);
   glutInitWindowPosition (100, 100);
   glutCreateWindow (argv[0]);
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutMouseFunc(mouse);
   glutMainLoop();
   return 0;
}

gluProject() is another Utility Library routine, which is related to gluUnProject().gluProject() mimics the actions of the transformation pipeline. Given three-dimensional world coordinates and all the transformations that affect them, gluProject() returns the transformed window coordinates.

int gluProject(GLdouble objx, GLdouble objy, GLdouble objz, const GLdouble modelMatrix[16], const GLdouble projMatrix[16], const GLint viewport[4], GLdouble *winx, GLdouble *winy, GLdouble *winz);
Map the specified object coordinates (objx, objy, objz) into window coordinates, using transformations defined by a modelview matrix (modelMatrix), projection matrix (projMatrix), and viewport (viewport). The resulting window coordinates are returned in winx, winy, and winz. The function returns GL_TRUE, indicating success, or GL_FALSE, indicating failure.


Chapter 4 - OpenGL Programming Guide

OpenGL Programming Guide
Programming Guide > Chapter 4

Chapter 4
Color

Chapter Objectives

After reading this chapter, you'll be able to do the following:

  • Decide between using RGBA or color-index mode for your application
  • Specify desired colors for drawing objects
  • Use smooth shading to draw a single polygon with more than one color

The goal of almost all OpenGL applications is to draw color pictures in a window on the screen. The window is a rectangular array of pixels, each of which contains and displays its own color. Thus, in a sense, the point of all the calculations performed by an OpenGL implementation - calculations that take into account OpenGL commands, state information, and values of parameters - is to determine the final color of every pixel that's to be drawn in the window. This chapter explains the commands for specifying colors and how OpenGL interprets them in the following major sections:

  • "Color Perception" discusses how the eye perceives color.
  • "Computer Color" describes the relationship between pixels on a computer monitor and their colors; it also defines the two display modes, RGBA and color index.
  • "RGBA versus Color-Index Mode" explains how the two display modes use graphics hardware and how to decide which mode to use.
  • "Specifying a Color and a Shading Model" describes the OpenGL commands you use to specify the desired color or shading model.

Color Perception

Physically, light is composed of photons - tiny particles of light, each traveling along its own path, and each vibrating at its own frequency (or wavelength, or energy - any one of frequency, wavelength, or energy determines the others). A photon is completely characterized by its position, direction, and frequency/wavelength/energy. Photons with wavelengths ranging from about 390 nanometers (nm) (violet) and 720 nm (red) cover the colors of the visible spectrum, forming the colors of a rainbow (violet, indigo, blue, green, yellow, orange, red). However, your eyes perceive lots of colors that aren't in the rainbow - white, black, brown, and pink, for example. How does this happen?

What your eye actually sees is a mixture of photons of different frequencies. Real light sources are characterized by the distribution of photon frequencies they emit. Ideal white light consists of an equal amount of light of all frequencies. Laser light is usually very pure, and all photons have almost identical frequencies (and direction and phase, as well). Light from a sodium-vapor lamp has more light in the yellow frequency. Light from most stars in space has a distribution that depends heavily on their temperatures (black-body radiation). The frequency distribution of light from most sources in your immediate environment is more complicated.

The human eye perceives color when certain cells in the retina (called cone cells, or just cones) become excited after being struck by photons. The three different kinds of cone cells respond best to three different wavelengths of light: one type of cone cell responds best to red light, one type to green, and the other to blue. (A person who is color-blind is usually missing one or more types of cone cells.) When a given mixture of photons enters the eye, the cone cells in the retina register different degrees of excitation depending on their types, and if a different mixture of photons comes in that happens to excite the three types of cone cells to the same degrees, its color is indistinguishable from that of the first mixture.

Since each color is recorded by the eye as the levels of excitation of the cone cells by the incoming photons, the eye can perceive colors that aren't in the spectrum produced by a prism or rainbow. For example, if you send a mixture of red and blue photons so that both the red and blue cones in the retina are excited, your eye sees it as magenta, which isn't in the spectrum. Other combinations give browns, turquoises, and mauves, none of which appear in the color spectrum.

A computer-graphics monitor emulates visible colors by lighting pixels with a combination of red, green, and blue light in proportions that excite the red-, green-, and blue-sensitive cones in the retina in such a way that it matches the excitation levels generated by the photon mix it's trying to emulate. If humans had more types of cone cells, some that were yellow-sensitive for example, color monitors would probably have a yellow gun as well, and we'd use RGBY (red, green, blue, yellow) quadruples to specify colors. And if everyone were color-blind in the same way, this chapter would be simpler.

To display a particular color, the monitor sends the right amounts of red, green, and blue light to appropriately stimulate the different types of cone cells in your eye. A color monitor can send different proportions of red, green, and blue to each of the pixels, and the eye sees a million or so pinpoints of light, each with its own color.

This section considers only how the eye perceives combinations of photons that enter it. The situation for light bouncing off materials and entering the eye is even more complex - white light bouncing off a red ball will appear red, or yellow light shining through blue glass appears almost black, for example. (See "Real-World and OpenGL Lighting" in Chapter 5 for a discussion of these effects.)

Computer Color

On a color computer screen, the hardware causes each pixel on the screen to emit different amounts of red, green, and blue light. These are called the R, G, and B values. They're often packed together (sometimes with a fourth value, called alpha, or A), and the packed value is called the RGB (or RGBA) value. (See "Blending" in Chapter 6 for an explanation of the alpha values.) The color information at each pixel can be stored either in RGBA mode, in which the R, G, B, and possibly A values are kept for each pixel, or in color-index mode, in which a single number (called the color index) is stored for each pixel. Each color index indicates an entry in a table that defines a particular set of R, G, and B values. Such a table is called a color map.

In color-index mode, you might want to alter the values in the color map. Since color maps are controlled by the window system, there are no OpenGL commands to do this. All the examples in this book initialize the color-display mode at the time the window is opened by using routines from the GLUT library. (See Appendix D for details.)

There is a great deal of variation among the different graphics hardware platforms in both the size of the pixel array and the number of colors that can be displayed at each pixel. On any graphics system, each pixel has the same amount of memory for storing its color, and all the memory for all the pixels is called the color buffer. The size of a buffer is usually measured in bits, so an 8-bit buffer could store 8 bits of data (256 possible different colors) for each pixel. The size of the possible buffers varies from machine to machine. (See Chapter 10 for more information.)

The R, G, and B values can range from 0.0 (none) to 1.0 (full intensity). For example, R = 0.0, G = 0.0, and B = 1.0 represents the brightest possible blue. If R, G, and B are all 0.0, the pixel is black; if all are 1.0, the pixel is drawn in the brightest white that can be displayed on the screen. Blending green and blue creates shades of cyan. Blue and red combine for magenta. Red and green create yellow. To help you create the colors you want from the R, G, and B components, look at the color cube shown in Plate 12. The axes of this cube represent intensities of red, blue, and green. A black-and-white version of the cube is shown in Figure 4-1.

Figure 4-1 : The Color Cube in Black and White

The commands to specify a color for an object (in this case, a point) can be as simple as this:

glColor3f (1.0, 0.0, 0.0);  /* the current RGB color is red: */
                            /* full red, no green, no blue. */
glBegin (GL_POINTS);
    glVertex3fv (point_array);
glEnd ();

In certain modes (for example, if lighting or texturing calculations are performed), the assigned color might go through other operations before arriving in the framebuffer as a value representing a color for a pixel. In fact, the color of a pixel is determined by a lengthy sequence of operations.

Early in a program's execution, the color-display mode is set to either RGBA mode or color-index mode. Once the color-display mode is initialized, it can't be changed. As the program executes, a color (either a color index or an RGBA value) is determined on a per-vertex basis for each geometric primitive. This color is either a color you've explicitly specified for a vertex or, if lighting is enabled, is determined from the interaction of the transformation matrices with the surface normals and other material properties. In other words, a red ball with a blue light shining on it looks different from the same ball with no light on it. (See Chapter 5 for details.) After the relevant lighting calculations are performed, the chosen shading model is applied. As explained in "Specifying a Color and a Shading Model," you can choose flat or smooth shading, each of which has different effects on the eventual color of a pixel.

Next, the primitives are rasterized, or converted to a two-dimensional image. Rasterizing involves determining which squares of an integer grid in window coordinates are occupied by the primitive and then assigning color and other values to each such square. A grid square along with its associated values of color, z (depth), and texture coordinates is called a fragment. Pixels are elements of the framebuffer; a fragment comes from a primitive and is combined with its corresponding pixel to yield a new pixel. Once a fragment is constructed, texturing, fog, and antialiasing are applied - if they're enabled - to the fragments. After that, any specified alpha blending, dithering, and bitwise logical operations are carried out using the fragment and the pixel already stored in the framebuffer. Finally, the fragment's color value (either color index or RGBA) is written into the pixel and displayed in the window using the window's color-display mode.

RGBA versus Color-Index Mode

In either color-index or RGBA mode, a certain amount of color data is stored at each pixel. This amount is determined by the number of bitplanes in the framebuffer. A bitplane contains 1 bit of data for each pixel. If there are 8color bitplanes, there are 8 color bits per pixel, and hence 28 = 256 different values or colors that can be stored at the pixel.

Bitplanes are often divided evenly into storage for R, G, and B components (that is, a 24-bitplane system devotes 8 bits each to red, green, and blue), but this isn't always true. To find out the number of bitplanes available on your system for red, green, blue, alpha, or color-index values, use glGetIntegerv() with GL_RED_BITS, GL_GREEN_BITS, GL_BLUE_BITS, GL_ALPHA_BITS, and GL_INDEX_BITS.

Note: Color intensities on most computer screens aren't perceived as linear by the human eye. Consider colors consisting of just a red component, with green and blue set to zero. As the intensity varies from 0.0 (off) to 1.0 (full on), the number of electrons striking the pixels increases, but the question is, does 0.5 look like halfway between 0.0 and 1.0? To test this, write a program that draws alternate pixels in a checkerboard pattern to intensities 0.0 and 1.0, and compare it with a region drawn solidly in color 0.5. From a reasonable distance from the screen, the two regions should appear to have the same intensity. If they look noticeably different, you need to use whatever correction mechanism is provided on your particular system. For example, many systems have a table to adjust intensities so that 0.5 appears to be halfway between 0.0 and 1.0. The mapping generally used is an exponential one, with the exponent referred to as gamma (hence the term gamma correction). Using the same gamma for the red, green, and blue components gives pretty good results, but three different gamma values might give slightly better results. (For more details on this topic, see Foley, van Dam, et al. Computer Graphics: Principles and Practice. Reading, MA: Addison-Wesley Developers Press, 1990.)

RGBA Display Mode

In RGBA mode, the hardware sets aside a certain number of bitplanes for each of the R, G, B, and A components (not necessarily the same number for each component) as shown in Figure 4-2. The R, G, and B values are typically stored as integers rather than floating-point numbers, and they're scaled to the number of available bits for storage and retrieval. For example, if a system has 8 bits available for the R component, integers between 0 and 255 can be stored; thus, 0, 1, 2, ..., 255 in the bitplanes would correspond to R values of 0/255 = 0.0, 1/255, 2/255, ..., 255/255 = 1.0. Regardless of the number of bitplanes, 0.0 specifies the minimum intensity, and 1.0 specifies the maximum intensity.

Figure 4-2 : RGB Values from the Bitplanes

Note: The alpha value (the A in RGBA) has no direct effect on the color displayed on the screen. It can be used for many things, including blending and transparency, and it can have an effect on the values of R, G, and B that are written. (See "Blending" in Chapter 6 for more information about alpha values.)

The number of distinct colors that can be displayed at a single pixel depends on the number of bitplanes and the capacity of the hardware to interpret those bitplanes. The number of distinct colors can't exceed 2n, where n is the number of bitplanes. Thus, a machine with 24 bitplanes for RGB can display up to 16.77 million distinct colors.

Dithering

Advanced

Some graphics hardware uses dithering to increase the number of apparent colors. Dithering is the technique of using combinations of some colors to create the effect of other colors. To illustrate how dithering works, suppose your system has only 1 bit each for R, G, and B and thus can display only eight colors: black, white, red, blue, green, yellow, cyan, and magenta. To display a pink region, the hardware can fill the region in a checkerboard manner, alternating red and white pixels. If your eye is far enough away from the screen that it can't distinguish individual pixels, the region appears pink - the average of red and white. Redder pinks can be achieved by filling a higher proportion of the pixels with red, whiter pinks would use more white pixels, and so on.

With this technique, there are no pink pixels. The only way to achieve the effect of "pinkness" is to cover a region consisting of multiple pixels - you can't dither a single pixel. If you specify an RGB value for an unavailable color and fill a polygon, the hardware fills the pixels in the interior of the polygon with a mixture of nearby colors whose average appears to your eye to be the color you want. (Remember, though, that if you're reading pixel information out of the framebuffer, you get the actual red and white pixel values, since there aren't any pink ones. See Chapter 8 for more information about reading pixel values.)

Figure 4-3 illustrates some simple dithering of black and white pixels to make shades of gray. From left to right, the 4 ´ 4 patterns at the top represent dithering patterns for 50 percent, 19 percent, and 69 percent gray. Under each pattern, you can see repeated reduced copies of each pattern, but these black and white squares are still bigger than most pixels. If you look at them from across the room, you can see that they blur together and appear as three levels of gray.

Figure 4-3 : Dithering Black and White to Create Gray

With about 8 bits each of R, G, and B, you can get a fairly high-quality image without dithering. Just because your machine has 24 color bitplanes, however, doesn't mean that dithering won't be desirable. For example, if you are running in double-buffer mode, the bitplanes might be divided into two sets of twelve, so there are really only 4 bits each per R, G, and B component. Without dithering, 4-bit-per-component color can give less than satisfactory results in many situations.

You enable or disable dithering by passing GL_DITHER to glEnable() or glDisable(). Note that dithering, unlike many other features, is enabled by default.

Color-Index Display Mode

With color-index mode, OpenGL uses a color map (or lookup table), which is similar to using a palette to mix paints to prepare for a paint-by-number scene. A painter's palette provides spaces to mix paints together; similarly, a computer's color map provides indices where the primary red, green, and blue values can be mixed, as shown in Figure 4-4.

Figure 4-4 : A Color Map

A painter filling in a paint-by-number scene chooses a color from the color palette and fills the corresponding numbered regions with that color. A computer stores the color index in the bitplanes for each pixel. Then those bitplane values reference the color map, and the screen is painted with the corresponding red, green, and blue values from the color map, as shown in Figure 4-5.

Figure 4-5 : Using a Color Map to Paint a Picture

In color-index mode, the number of simultaneously available colors is limited by the size of the color map and the number of bitplanes available. The size of the color map is determined by the amount of hardware dedicated to it. The size of the color map is always a power of 2, and typical sizes range from 256 (28) to 4096 (212), where the exponent is the number of bitplanes being used. If there are 2n indices in the color map and m available bitplanes, the number of usable entries is the smaller of 2n and 2m.

With RGBA mode, each pixel's color is independent of other pixels. However, in color-index mode, each pixel with the same index stored in its bitplanes shares the same color-map location. If the contents of an entry in the color map change, then all pixels of that color index change their color.

Choosing between RGBA and Color-Index Mode

You should base your decision to use RGBA or color-index mode on what hardware is available and on what your application needs. For most systems, more colors can be simultaneously represented with RGBA mode than with color-index mode. Also, for several effects, such as shading, lighting, texture mapping, and fog, RGBA provides more flexibility than color-index mode.

You might prefer to use color-index mode in the following cases:

  • If you're porting an existing application that makes significant use of color-index mode, it might be easier to not change to RGBA mode.
  • If you have a small number of bitplanes available, RGBA mode may produce noticeably coarse shades of colors. For example, if you have only 8 bitplanes, in RGBA mode, you may have only 3 bits for red, 3 bits for green, and 2 bits for blue. You'd only have 8 (23) shades of red and green, and only 4 shades of blue. The gradients between color shades are likely to be very obvious.

In this situation, if you have limited shading requirements, you can use the color lookup table to load more shades of colors. For example, if you need only shades of blue, you can use color-index mode and store up to 256 (28) shades of blue in the color-lookup table, which is much better than the 4 shades you would have in RGBA mode. Of course, this example would use up your entire color-lookup table, so you would have no shades of red, green, or other combined colors.
  • Color-index mode can be useful for various tricks, such as color-map animation and drawing in layers. (See Chapter 14 for more information.)

In general, use RGBA mode wherever possible. It works with texture mapping and works better with lighting, shading, fog, antialiasing, and blending.

Changing between Display Modes

In the best of all possible worlds, you might want to avoid making a choice between RGBA and color-index display mode. For example, you may want to use color-index mode for a color-map animation effect and then, when needed, immediately change the scene to RGBA mode for texture mapping.

Or similarly, you may desire to switch between single and double buffering. For example, you may have very few bitplanes; let's say 8 bitplanes. In single-buffer mode, you'll have 256 (28) colors, but if you are using double-buffer mode to eliminate flickering from your animated program, you may only have 16 (24) colors. Perhaps you want to draw a moving object without flicker and are willing to sacrifice colors for using double-buffer mode (maybe the object is moving so fast that the viewer won't notice the details). But when the object comes to rest, you will want to draw it in single-buffer mode so that you can use more colors.

Unfortunately, most window systems won't allow an easy switch. For example, with the X Window System, the color-display mode is an attribute of the X Visual. An X Visual must be specified before the window is created. Once it is specified, it cannot be changed for the life of the window. After you create a window with a double-buffered, RGBA display mode, you're stuck with it.

A tricky solution to this problem is to create more than one window, each with a different display mode. Then you must control the visibility of the windows (for example, mapping or unmapping an X Window, or managing or unmanaging a Motif or Athena widget) and draw the object into the appropriate, visible window.

Specifying a Color and a Shading Model

OpenGL maintains a current color (in RGBA mode) and a current color index (in color-index mode). Unless you're using a more complicated coloring model such as lighting or texture mapping, each object is drawn using the current color (or color index). Look at the following pseudocode sequence:

set_color(RED);
draw_item(A);
draw_item(B);
set_color(GREEN);
set_color(BLUE);
draw_item(C);

Items A and B are drawn in red, and item C is drawn in blue. The fourth line, which sets the current color to green, has no effect (except to waste a bit of time). With no lighting or texturing, when the current color is set, all items drawn afterward are drawn in that color until the current color is changed to something else.

Specifying a Color in RGBA Mode

In RGBA mode, use the glColor*() command to select a current color.

void glColor3{b s i f d ub us ui} (TYPEr, TYPEg, TYPEb);
void glColor4{b s i f d ub us ui} (TYPEr, TYPEg, TYPEb, TYPEa);
void glColor3{b s i f d ub us ui}v (const TYPE*v);
void glColor4{b s i f d ub us ui}v (const TYPE*v);
Sets the current red, green, blue, and alpha values. This command can have up to three suffixes, which differentiate variations of the parameters accepted. The first suffix is either 3 or 4, to indicate whether you supply an alpha value in addition to the red, green, and blue values. If you don't supply an alpha value, it's automatically set to 1.0. The second suffix indicates the data type for parameters: byte, short, integer, float, double, unsigned byte, unsigned short, or unsigned integer. The third suffix is an optional v, which indicates that the argument is a pointer to an array of values of the given data type.

For the versions of glColor*() that accept floating-point data types, the values should typically range between 0.0 and 1.0, the minimum and maximum values that can be stored in the framebuffer. Unsigned-integer color components, when specified, are linearly mapped to floating-point values such that the largest representable value maps to 1.0 (full intensity), and zero maps to 0.0 (zero intensity). Signed-integer color components, when specified, are linearly mapped to floating-point values such that the most positive representable value maps to 1.0, and the most negative representable value maps to -1.0 (see Table 4-1).

Neither floating-point nor signed-integer values are clamped to the range [0,1] before updating the current color or current lighting material parameters. After lighting calculations, resulting color values outside the range [0,1] are clamped to the range [0,1] before they are interpolated or written into a color buffer. Even if lighting is disabled, the color components are clamped before rasterization.

Table 4-1 : Converting Color Values to Floating-Point Numbers

Suffix | Data Type | Minimum Value | Min Value Maps to | Maximum Value | Max Value Maps to
b | 1-byte integer | -128 | -1.0 | 127 | 1.0
s | 2-byte integer | -32,768 | -1.0 | 32,767 | 1.0
i | 4-byte integer | -2,147,483,648 | -1.0 | 2,147,483,647 | 1.0
ub | unsigned 1-byte integer | 0 | 0.0 | 255 | 1.0
us | unsigned 2-byte integer | 0 | 0.0 | 65,535 | 1.0
ui | unsigned 4-byte integer | 0 | 0.0 | 4,294,967,295 | 1.0

Specifying a Color in Color-Index Mode

In color-index mode, use the glIndex*() command to select a single-valued color index as the current color index.

void glIndex{sifd ub}(TYPE c);
void glIndex{sifd ub}v(const TYPE *c);
Sets the current color index to c. The first suffix for this command indicates the data type for parameters: short, integer, float, double, or unsigned byte. The second, optional suffix is v, which indicates that the argument is an array of values of the given data type (the array contains only one value).

In "Clearing the Window" in Chapter 2, you saw the specification of glClearColor(). For color-index mode, there is a corresponding glClearIndex().

void glClearIndex(GLfloat cindex);
Sets the current clearing color in color-index mode. In a color-index mode window, a call to glClear(GL_COLOR_BUFFER_BIT) will use cindex to clear the buffer. The default clearing index is 0.0.

Note: OpenGL does not have any routines to load values into the color-lookup table. Window systems typically already have such operations. GLUT has the routine glutSetColor() to call the window-system specific commands.

Advanced

The current index is stored as a floating-point value. Integer values are converted directly to floating-point values, with no special mapping. Index values outside the representable range of the color-index buffer aren't clamped. However, before an index is dithered (if enabled) and written to the framebuffer, it's converted to fixed-point format. Any bits in the integer portion of the resulting fixed-point value that don't correspond to bits in the framebuffer are masked out.

Specifying a Shading Model

A line or a filled polygon primitive can be drawn with a single color (flat shading) or with many different colors (smooth shading, also called Gouraud shading). You specify the desired shading technique with glShadeModel().

void glShadeModel (GLenum mode);
Sets the shading model. The mode parameter can be either GL_SMOOTH (the default) or GL_FLAT.

With flat shading, the color of one particular vertex of an independent primitive is duplicated across all the primitive's vertices to render that primitive. With smooth shading, the color at each vertex is treated individually. For a line primitive, the colors along the line segment are interpolated between the vertex colors. For a polygon primitive, the colors for the interior of the polygon are interpolated between the vertex colors. Example 4-1 draws a smooth-shaded triangle, as shown in "Plate 11" in Appendix I.

Example 4-1 : Drawing a Smooth-Shaded Triangle: smooth.c

#include <GL/gl.h>
#include <GL/glut.h>

void init(void)
{
   glClearColor (0.0, 0.0, 0.0, 0.0);
   glShadeModel (GL_SMOOTH);
}

void triangle(void)
{
   glBegin (GL_TRIANGLES);
   glColor3f (1.0, 0.0, 0.0);
   glVertex2f (5.0, 5.0);
   glColor3f (0.0, 1.0, 0.0);
   glVertex2f (25.0, 5.0);
   glColor3f (0.0, 0.0, 1.0);
   glVertex2f (5.0, 25.0);
   glEnd();
}

void display(void)
{
   glClear (GL_COLOR_BUFFER_BIT);
   triangle ();
   glFlush ();
}

void reshape (int w, int h)
{
   glViewport (0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode (GL_PROJECTION);
   glLoadIdentity ();
   if (w <= h)
      gluOrtho2D (0.0, 30.0, 0.0, 30.0*(GLfloat) h/(GLfloat) w);
   else
      gluOrtho2D (0.0, 30.0*(GLfloat) w/(GLfloat) h, 0.0, 30.0);
   glMatrixMode(GL_MODELVIEW);
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
   glutInitWindowSize (500, 500);
   glutInitWindowPosition (100, 100);
   glutCreateWindow (argv[0]);
   init ();
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutMainLoop();
   return 0;
}

With smooth shading, neighboring pixels have slightly different color values. In RGBA mode, adjacent pixels with slightly different values look similar, so the color changes across a polygon appear gradual. In color-index mode, adjacent pixels may reference different locations in the color-index table, which may not have similar colors at all. Adjacent color-index entries may contain wildly different colors, so a smooth-shaded polygon in color-index mode can look psychedelic.

To avoid this problem, you have to create a color ramp of smoothly changing colors among a contiguous set of indices in the color map. Remember that loading colors into a color map is performed through your window system rather than OpenGL. If you use GLUT, you can use glutSetColor() to load a single index in the color map with specified red, green, and blue values. The first argument for glutSetColor() is the index, and the others are the red, green, and blue values. To load thirty-two contiguous color indices (from color index 16 to 47) with slightly differing shades of yellow, you might call

for (i = 0; i < 32; i++) {
   glutSetColor (16+i, 1.0*(i/32.0), 1.0*(i/32.0), 0.0);
}

Now, if you render smooth-shaded polygons that use only the colors from index 16 to 47, those polygons have gradually differing shades of yellow.

With flat shading, the color of a single vertex defines the color of an entire primitive. For a line segment, the color of the line is the current color when the second (ending) vertex is specified. For a polygon, the color used is the one that's in effect when a particular vertex is specified, as shown in Table 4-2. The table counts vertices and polygons starting from 1. OpenGL follows these rules consistently, but the best way to avoid uncertainty about how a flat-shaded primitive will be drawn is to specify only one color for the primitive.

Table 4-2 : How OpenGL Selects a Color for the ith Flat-Shaded Polygon

Type of Polygon | Vertex Used to Select the Color for the ith Polygon
single polygon | 1
triangle strip | i+2
triangle fan | i+2
independent triangle | 3i
quad strip | 2i+2
independent quad | 4i


Chapter 5 - OpenGL Programming Guide

OpenGL Programming Guide
Programming Guide > Chapter 5

Chapter 5
Lighting

Chapter Objectives

After reading this chapter, you'll be able to do the following:

  • Understand how real-world lighting conditions are approximated by OpenGL
  • Render illuminated objects by defining the desired light sources and lighting model
  • Define the material properties of the objects being illuminated
  • Manipulate the matrix stack to control the position of light sources

As you saw in Chapter 4, OpenGL computes the color of each pixel in a final, displayed scene that's held in the framebuffer. Part of this computation depends on what lighting is used in the scene and on how objects in the scene reflect or absorb that light. As an example of this, recall that the ocean has a different color on a bright, sunny day than it does on a gray, cloudy day. The presence of sunlight or clouds determines whether you see the ocean as bright turquoise or murky gray-green. In fact, most objects don't even look three-dimensional until they're lit. Figure 5-1 shows two versions of the exact same scene (a single sphere), one with lighting and one without.

Figure 5-1 : A Lit and an Unlit Sphere

As you can see, an unlit sphere looks no different from a two-dimensional disk. This demonstrates how critical the interaction between objects and light is in creating a three-dimensional scene.

With OpenGL, you can manipulate the lighting and objects in a scene to create many different kinds of effects. This chapter begins with a primer on hidden-surface removal. Then it explains how to control the lighting in a scene, discusses the OpenGL conceptual model of lighting, and describes in detail how to set the numerous illumination parameters to achieve certain effects. Toward the end of the chapter, the mathematical computations that determine how lighting affects color are presented.

This chapter contains the following major sections:

  • "A Hidden-Surface Removal Survival Kit" describes the basics of removing hidden surfaces from view.
  • "Real-World and OpenGL Lighting" explains in general terms how light behaves in the world and how OpenGL models this behavior.
  • "A Simple Example: Rendering a Lit Sphere" introduces the OpenGL lighting facility by presenting a short program that renders a lit sphere.
  • "Creating Light Sources" explains how to define and position light sources.
  • "Selecting a Lighting Model" discusses the elements of a lighting model and how to specify them.
  • "Defining Material Properties" explains how to describe the properties of objects so that they interact with light in a desired way.
  • "The Mathematics of Lighting" presents the mathematical calculations used by OpenGL to determine the effect of lights in a scene.
  • "Lighting in Color-Index Mode" discusses the differences between using RGBA mode and color-index mode for lighting.

A Hidden-Surface Removal Survival Kit

With this section, you begin to draw shaded, three-dimensional objects, in earnest. With shaded polygons, it becomes very important to draw the objects that are closer to our viewing position and to eliminate objects obscured by others nearer to the eye.

When you draw a scene composed of three-dimensional objects, some of them might obscure all or parts of others. Changing your viewpoint can change the obscuring relationship. For example, if you view the scene from the opposite direction, any object that was previously in front of another is now behind it. To draw a realistic scene, these obscuring relationships must be maintained. Suppose your code works like this:

while (1) {
   get_viewing_point_from_mouse_position();
   glClear(GL_COLOR_BUFFER_BIT);
   draw_3d_object_A();
   draw_3d_object_B();
}

For some mouse positions, object A might obscure object B. For others, the reverse may hold. If nothing special is done, the preceding code always draws object B second (and thus on top of object A) no matter what viewing position is selected. In a worst case scenario, if objects A and B intersect one another so that part of object A obscures object B and part of B obscures A, changing the drawing order does not provide a solution.

The elimination of parts of solid objects that are obscured by others is called hidden-surface removal. (Hidden-line removal, which does the same job for objects represented as wireframe skeletons, is a bit trickier and isn't discussed here. See "Hidden-Line Removal" in Chapter 14 for details.) The easiest way to achieve hidden-surface removal is to use the depth buffer (sometimes called a z-buffer). (Also see Chapter 10.)

A depth buffer works by associating a depth, or distance, from the view plane (usually the near clipping plane), with each pixel on the window. Initially, the depth values for all pixels are set to the largest possible distance (usually the far clipping plane) using the glClear() command with GL_DEPTH_BUFFER_BIT. Then the objects in the scene are drawn in any order.

Graphical calculations in hardware or software convert each surface that's drawn to a set of pixels on the window where the surface will appear if it isn't obscured by something else. In addition, the distance from the view plane is computed. With depth buffering enabled, before each pixel is drawn a comparison is done with the depth value already stored at the pixel. If the new pixel is closer than (in front of) what's there, the new pixel's color and depth values replace those that are currently written into the pixel. If the new pixel's depth is greater than what's currently there, the new pixel is obscured, and the color and depth information for the incoming pixel is discarded.

To use depth buffering, you need to enable depth buffering. This has to be done only once. Before drawing, each time you draw the scene, you need to clear the depth buffer and then draw the objects in the scene in any order.

To convert the preceding code example so that it performs hidden-surface removal, modify it to the following:

glutInitDisplayMode (GLUT_DEPTH | .... );
glEnable(GL_DEPTH_TEST);
...
while (1) {
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   get_viewing_point_from_mouse_position();
   draw_3d_object_A();
   draw_3d_object_B();
}

The argument to glClear() clears both the depth and color buffers.

Depth-buffer testing can affect the performance of your application. Since information is discarded rather than used for drawing, hidden-surface removal can increase your performance slightly. However, the implementation of your depth buffer probably has the greatest effect on performance. A "software" depth buffer (implemented with processor memory) may be much slower than one implemented with a specialized hardware depth buffer.

Real-World and OpenGL Lighting

When you look at a physical surface, your eye's perception of the color depends on the distribution of photon energies that arrive and trigger your cone cells. (See "Color Perception" in Chapter 4.) Those photons come from a light source or combination of sources, some of which are absorbed and some of which are reflected by the surface. In addition, different surfaces may have very different properties - some are shiny and preferentially reflect light in certain directions, while others scatter incoming light equally in all directions. Most surfaces are somewhere in between.

OpenGL approximates light and lighting as if light can be broken into red, green, and blue components. Thus, the color of light sources is characterized by the amount of red, green, and blue light they emit, and the material of surfaces is characterized by the percentage of the incoming red, green, and blue components that is reflected in various directions. The OpenGL lighting equations are just an approximation but one that works fairly well and can be computed relatively quickly. If you desire a more accurate (or just different) lighting model, you have to do your own calculations in software. Such software can be enormously complex, as a few hours of reading any optics textbook should convince you.

In the OpenGL lighting model, the light in a scene comes from several light sources that can be individually turned on and off. Some light comes from a particular direction or position, and some light is generally scattered about the scene. For example, when you turn on a light bulb in a room, most of the light comes from the bulb, but some light comes after bouncing off one, two, three, or more walls. This bounced light (called ambient) is assumed to be so scattered that there is no way to tell its original direction, but it disappears if a particular light source is turned off.

Finally, there might be a general ambient light in the scene that comes from no particular source, as if it had been scattered so many times that its original source is impossible to determine.

In the OpenGL model, the light sources have an effect only when there are surfaces that absorb and reflect light. Each surface is assumed to be composed of a material with various properties. A material might emit its own light (like headlights on an automobile), it might scatter some incoming light in all directions, and it might reflect some portion of the incoming light in a preferential direction like a mirror or other shiny surface.

The OpenGL lighting model considers the lighting to be divided into four independent components: emissive, ambient, diffuse, and specular. All four components are computed independently and then added together.

Ambient, Diffuse, and Specular Light

Ambient illumination is light that's been scattered so much by the environment that its direction is impossible to determine - it seems to come from all directions. Backlighting in a room has a large ambient component, since most of the light that reaches your eye has first bounced off many surfaces. A spotlight outdoors has a tiny ambient component; most of the light travels in the same direction, and since you're outdoors, very little of the light reaches your eye after bouncing off other objects. When ambient light strikes a surface, it's scattered equally in all directions.

The diffuse component is the light that comes from one direction, so it's brighter if it comes squarely down on a surface than if it barely glances off the surface. Once it hits a surface, however, it's scattered equally in all directions, so it appears equally bright, no matter where the eye is located. Any light coming from a particular position or direction probably has a diffuse component.

Finally, specular light comes from a particular direction, and it tends to bounce off the surface in a preferred direction. A well-collimated laser beam bouncing off a high-quality mirror produces almost 100 percent specular reflection. Shiny metal or plastic has a high specular component, and chalk or carpet has almost none. You can think of specularity as shininess.

Although a light source delivers a single distribution of frequencies, the ambient, diffuse, and specular components might be different. For example, if you have a white light in a room with red walls, the scattered light tends to be red, although the light directly striking objects is white. OpenGL allows you to set the red, green, and blue values for each component of light independently.

Material Colors

The OpenGL lighting model makes the approximation that a material's color depends on the percentages of the incoming red, green, and blue light it reflects. For example, a perfectly red ball reflects all the incoming red light and absorbs all the green and blue light that strikes it. If you view such a ball in white light (composed of equal amounts of red, green, and blue light), all the red is reflected, and you see a red ball. If the ball is viewed in pure red light, it also appears to be red. If, however, the red ball is viewed in pure green light, it appears black (all the green is absorbed, and there's no incoming red, so no light is reflected).

Like lights, materials have different ambient, diffuse, and specular colors, which determine the ambient, diffuse, and specular reflectances of the material. A material's ambient reflectance is combined with the ambient component of each incoming light source, the diffuse reflectance with the light's diffuse component, and similarly for the specular reflectance and component. Ambient and diffuse reflectances define the color of the material and are typically similar if not identical. Specular reflectance is usually white or gray, so that specular highlights end up being the color of the light source's specular intensity. If you think of a white light shining on a shiny red plastic sphere, most of the sphere appears red, but the shiny highlight is white.

In addition to ambient, diffuse, and specular colors, materials have an emissive color, which simulates light originating from an object. In the OpenGL lighting model, the emissive color of a surface adds intensity to the object, but is unaffected by any light sources. Also, the emissive color does not introduce any additional light into the overall scene.

RGB Values for Lights and Materials

The color components specified for lights mean something different than for materials. For a light, the numbers correspond to a percentage of full intensity for each color. If the R, G, and B values for a light's color are all 1.0, the light is the brightest possible white. If the values are 0.5, the color is still white, but only at half intensity, so it appears gray. If R=G=1 and B=0 (full red and green with no blue), the light appears yellow.

For materials, the numbers correspond to the reflected proportions of those colors. So if R=1, G=0.5, and B=0 for a material, that material reflects all the incoming red light, half the incoming green, and none of the incoming blue light. In other words, if an OpenGL light has components (LR, LG, LB), and a material has corresponding components (MR, MG, MB), then, ignoring all other reflectivity effects, the light that arrives at the eye is given by (LR*MR, LG*MG, LB*MB).

Similarly, if you have two lights that send (R1, G1, B1) and (R2, G2, B2) to the eye, OpenGL adds the components, giving (R1+R2, G1+G2, B1+B2). If any of the sums are greater than 1 (corresponding to a color brighter than the equipment can display), the component is clamped to 1.

A Simple Example: Rendering a Lit Sphere

These are the steps required to add lighting to your scene.

Define normal vectors for each vertex of all the objects. These normals determine the orientation of the object relative to the light sources.

Create, select, and position one or more light sources.

Create and select a lighting model, which defines the level of global ambient light and the effective location of the viewpoint (for the purposes of lighting calculations).

Define material properties for the objects in the scene.

Example 5-1 accomplishes these tasks. It displays a sphere illuminated by a single light source, as shown earlier in Figure 5-1.

Example 5-1 : Drawing a Lit Sphere: light.c

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>

void init(void)
{
   GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
   GLfloat mat_shininess[] = { 50.0 };
   GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };
   glClearColor (0.0, 0.0, 0.0, 0.0);
   glShadeModel (GL_SMOOTH);

   glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
   glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
   glLightfv(GL_LIGHT0, GL_POSITION, light_position);

   glEnable(GL_LIGHTING);
   glEnable(GL_LIGHT0);
   glEnable(GL_DEPTH_TEST);
}

void display(void)
{
   glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   glutSolidSphere (1.0, 20, 16);
   glFlush ();
}

void reshape (int w, int h)
{
   glViewport (0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode (GL_PROJECTION);
   glLoadIdentity();
   if (w <= h)
      glOrtho (-1.5, 1.5, -1.5*(GLfloat)h/(GLfloat)w,
         1.5*(GLfloat)h/(GLfloat)w, -10.0, 10.0);
   else
      glOrtho (-1.5*(GLfloat)w/(GLfloat)h,
         1.5*(GLfloat)w/(GLfloat)h, -1.5, 1.5, -10.0, 10.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);
   glutInitWindowSize (500, 500);
   glutInitWindowPosition (100, 100);
   glutCreateWindow (argv[0]);
   init ();
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutMainLoop();
   return 0;
}

The lighting-related calls are in the init() command; they're discussed briefly in the following paragraphs and in more detail later in the chapter. One thing to note about Example 5-1 is that it uses RGBA color mode, not color-index mode. The OpenGL lighting calculation is different for the two modes, and in fact the lighting capabilities are more limited in color-index mode. Thus, RGBA is the preferred mode when doing lighting, and all the examples in this chapter use it. (See "Lighting in Color-Index Mode" for more information about lighting in color-index mode.)

Define Normal Vectors for Each Vertex of Every Object

An object's normals determine its orientation relative to the light sources. For each vertex, OpenGL uses the assigned normal to determine how much light that particular vertex receives from each light source. In this example, the normals for the sphere are defined as part of the glutSolidSphere() routine. (See "Normal Vectors" in Chapter 2 for more details on how to define normals.)

Create, Position, and Enable One or More Light Sources

Example 5-1 uses only one, white light source; its location is specified by the glLightfv() call. This example uses the default color for light zero (GL_LIGHT0), which is white; if you want a differently colored light, use glLight*() to indicate this. You can include at least eight different light sources in your scene of various colors; the default color of these other lights is black. (The particular implementation of OpenGL you're using might allow more than eight.) You can also locate the lights wherever you desire - you can position them near the scene, as a desk lamp would be, or an infinite distance away, like the sun. In addition, you can control whether a light produces a narrow, focused beam or a wider beam. Remember that each light source adds significantly to the calculations needed to render the scene, so performance is affected by the number of lights in the scene. (See "Creating Light Sources" for more information about how to create lights with the desired characteristics.)

After you've defined the characteristics of the lights you want, you have to turn them on with the glEnable() command. You also need to call glEnable() with GL_LIGHTING as a parameter to prepare OpenGL to perform lighting calculations. (See "Enabling Lighting" for more information.)

Select a Lighting Model

As you might expect, the glLightModel*() command describes the parameters of a lighting model. In Example 5-1, the only element of the lighting model that's defined explicitly is the global ambient light. The lighting model also defines whether the viewer of the scene should be considered to be an infinite distance away or local to the scene, and whether lighting calculations should be performed differently for the front and back surfaces of objects in the scene. Example 5-1 uses the default settings for these two aspects of the model - an infinite viewer and one-sided lighting. Using a local viewer adds significantly to the complexity of the calculations that must be performed, because OpenGL must calculate the angle between the viewpoint and each object. With an infinite viewer, however, the angle is ignored, and the results are slightly less realistic. Further, since in this example, the back surface of the sphere is never seen (it's the inside of the sphere), one-sided lighting is sufficient. (See "Selecting a Lighting Model" for a more detailed description of the elements of an OpenGL lighting model.)

Define Material Properties for the Objects in the Scene

An object's material properties determine how it reflects light and therefore what material it seems to be made of. Because the interaction between an object's material surface and incident light is complex, specifying material properties so that an object has a certain desired appearance is an art. You can specify a material's ambient, diffuse, and specular colors and how shiny it is. In this example, only these last two material properties - the specular material color and shininess - are explicitly specified (with the glMaterialfv() calls). (See "Defining Material Properties" for a description and examples of all the material-property parameters.)

Some Important Notes

As you write your own lighting program, remember that you can use the default values for some lighting parameters; others need to be changed. Also, don't forget to enable whatever lights you define and to enable lighting calculations. Finally, remember that you might be able to use display lists to maximize efficiency as you change lighting conditions. (See "Display-List Design Philosophy" in Chapter 7.)

Creating Light Sources

Light sources have a number of properties, such as color, position, and direction. The following sections explain how to control these properties and what the resulting light looks like. The command used to specify all properties of lights is glLight*(); it takes three arguments: to identify the light whose property is being specified, the property, and the desired value for that property.

void glLight{if}(GLenum light, GLenum pname, TYPEparam);
void glLight{if}v(GLenum light, GLenum pname, TYPE *param);
Creates the light specified by light, which can be GL_LIGHT0, GL_LIGHT1, ... , or GL_LIGHT7. The characteristic of the light being set is defined by pname, which specifies a named parameter (see Table 5-1). param indicates the values to which the pname characteristic is set; it's a pointer to a group of values if the vector version is used, or the value itself if the nonvector version is used. The nonvector version can be used to set only single-valued light characteristics.
Table 5-1 : Default Values for pname Parameter of glLight*()
Parameter Name | Default Value | Meaning
GL_AMBIENT | (0.0, 0.0, 0.0, 1.0) | ambient RGBA intensity of light
GL_DIFFUSE | (1.0, 1.0, 1.0, 1.0) | diffuse RGBA intensity of light
GL_SPECULAR | (1.0, 1.0, 1.0, 1.0) | specular RGBA intensity of light
GL_POSITION | (0.0, 0.0, 1.0, 0.0) | (x, y, z, w) position of light
GL_SPOT_DIRECTION | (0.0, 0.0, -1.0) | (x, y, z) direction of spotlight
GL_SPOT_EXPONENT | 0.0 | spotlight exponent
GL_SPOT_CUTOFF | 180.0 | spotlight cutoff angle
GL_CONSTANT_ATTENUATION | 1.0 | constant attenuation factor
GL_LINEAR_ATTENUATION | 0.0 | linear attenuation factor
GL_QUADRATIC_ATTENUATION | 0.0 | quadratic attenuation factor

Note: The default values listed for GL_DIFFUSE and GL_SPECULAR in Table 5-1 apply only to GL_LIGHT0. For other lights, the default value is (0.0, 0.0, 0.0, 1.0) for both GL_DIFFUSE and GL_SPECULAR.

Example 5-2 shows how to use glLight*():

Example 5-2 : Defining Colors and Position for a Light Source

GLfloat light_ambient[] = { 0.0, 0.0, 0.0, 1.0 };
GLfloat light_diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat light_specular[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };

glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
glLightfv(GL_LIGHT0, GL_POSITION, light_position);

As you can see, arrays are defined for the parameter values, and glLightfv() is called repeatedly to set the various parameters. In this example, the first three calls to glLightfv() are superfluous, since they're being used to specify the default values for the GL_AMBIENT, GL_DIFFUSE, and GL_SPECULAR parameters.

Note: Remember to turn on each light with glEnable(). (See "Enabling Lighting" for more information about how to do this.)

All the parameters for glLight*() and their possible values are explained in the following sections. These parameters interact with those that define the overall lighting model for a particular scene and an object's material properties. (See "Selecting a Lighting Model" and "Defining Material Properties" for more information about these two topics. "The Mathematics of Lighting" explains how all these parameters interact mathematically.)

Color

OpenGL allows you to associate three different color-related parameters - GL_AMBIENT, GL_DIFFUSE, and GL_SPECULAR - with any particular light. The GL_AMBIENT parameter refers to the RGBA intensity of the ambient light that a particular light source adds to the scene. As you can see in Table 5-1, by default there is no ambient light since GL_AMBIENT is (0.0, 0.0, 0.0, 1.0). This value was used in Example 5-1. If this program had specified blue ambient light as

GLfloat light_ambient[] = { 0.0, 0.0, 1.0, 1.0};
glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);

the result would have been as shown in the left side of "Plate 13" in Appendix I.

The GL_DIFFUSE parameter probably most closely correlates with what you naturally think of as "the color of a light." It defines the RGBA color of the diffuse light that a particular light source adds to a scene. By default, GL_DIFFUSE is (1.0, 1.0, 1.0, 1.0) for GL_LIGHT0, which produces a bright, white light as shown in the left side of "Plate 13" in Appendix I. The default value for any other light (GL_LIGHT1, ... , GL_LIGHT7) is (0.0, 0.0, 0.0, 0.0).

The GL_SPECULAR parameter affects the color of the specular highlight on an object. Typically, a real-world object such as a glass bottle has a specular highlight that's the color of the light shining on it (which is often white). Therefore, if you want to create a realistic effect, set the GL_SPECULAR parameter to the same value as the GL_DIFFUSE parameter. By default, GL_SPECULAR is (1.0, 1.0, 1.0, 1.0) for GL_LIGHT0 and (0.0, 0.0, 0.0, 0.0) for any other light.

Note: The alpha component of these colors is not used until blending is enabled. (See Chapter 6.) Until then, the alpha value can be safely ignored.

Position and Attenuation

As previously mentioned, you can choose whether to have a light source that's treated as though it's located infinitely far away from the scene or one that's nearer to the scene. The first type is referred to as a directional light source; the effect of an infinite location is that the rays of light can be considered parallel by the time they reach an object. An example of a real-world directional light source is the sun. The second type is called a positional light source, since its exact position within the scene determines the effect it has on a scene and, specifically, the direction from which the light rays come. A desk lamp is an example of a positional light source. You can see the difference between directional and positional lights in "Plate 12" in Appendix I. The light used in Example 5-1 is a directional one:

GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };
glLightfv(GL_LIGHT0, GL_POSITION, light_position);

As shown, you supply a vector of four values (x, y, z, w) for the GL_POSITION parameter. If the last value, w, is zero, the corresponding light source is a directional one, and the (x, y, z) values describe its direction. This direction is transformed by the modelview matrix. By default, GL_POSITION is (0, 0, 1, 0), which defines a directional light that points along the negative z-axis. (Note that nothing prevents you from creating a directional light with the direction of (0, 0, 0), but such a light won't help you much.)

If the w value is nonzero, the light is positional, and the (x, y, z) values specify the location of the light in homogeneous object coordinates. (See Appendix F.) This location is transformed by the modelview matrix and stored in eye coordinates. (See "Controlling a Light's Position and Direction" for more information about how to control the transformation of the light's location.) Also, by default, a positional light radiates in all directions, but you can restrict it to producing a cone of illumination by defining the light as a spotlight. (See "Spotlights" for an explanation of how to define a light as a spotlight.)

Note: Remember that the colors across the face of a smooth-shaded polygon are determined by the colors calculated for the vertices. Because of this, you probably want to avoid using large polygons with local lights. If you locate the light near the middle of the polygon, the vertices might be too far away to receive much light, and the whole polygon will look darker than you intended. To avoid this problem, break up the large polygon into smaller ones.

For real-world lights, the intensity of light decreases as distance from the light increases. Since a directional light is infinitely far away, it doesn't make sense to attenuate its intensity over distance, so attenuation is disabled for a directional light. However, you might want to attenuate the light from a positional light. OpenGL attenuates a light source by multiplying the contribution of that source by an attenuation factor:

where

d = distance between the light's position and the vertex

kc = GL_CONSTANT_ATTENUATION

kl = GL_LINEAR_ATTENUATION

kq = GL_QUADRATIC_ATTENUATION

By default, kc is 1.0 and both kl and kq are zero, but you can give these parameters different values:

glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 2.0);
glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 1.0);
glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 0.5);

Note that the ambient, diffuse, and specular contributions are all attenuated. Only the emission and global ambient values aren't attenuated. Also note that since attenuation requires an additional division (and possibly more math) for each calculated color, using attenuated lights may slow down application performance.

Spotlights

As previously mentioned, you can have a positional light source act as a spotlight - that is, by restricting the shape of the light it emits to a cone. To create a spotlight, you need to determine the spread of the cone of light you desire. (Remember that since spotlights are positional lights, you also have to locate them where you want them. Again, note that nothing prevents you from creating a directional spotlight, but it won't give you the result you want.) To specify the angle between the axis of the cone and a ray along the edge of the cone, use the GL_SPOT_CUTOFF parameter. The angle of the cone at the apex is then twice this value, as shown in Figure 5-2.

Figure 5-2 : GL_SPOT_CUTOFF Parameter

Note that no light is emitted beyond the edges of the cone. By default, the spotlight feature is disabled because the GL_SPOT_CUTOFF parameter is 180.0. This value means that light is emitted in all directions (the angle at the cone's apex is 360 degrees, so it isn't a cone at all). The value for GL_SPOT_CUTOFF is restricted to being within the range [0.0,90.0] (unless it has the special value 180.0). The following line sets the cutoff parameter to 45 degrees:

glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 45.0);

You also need to specify a spotlight's direction, which determines the axis of the cone of light:

GLfloat spot_direction[] = { -1.0, -1.0, 0.0 };
glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, spot_direction);

The direction is specified in object coordinates. By default, the direction is (0.0, 0.0, -1.0), so if you don't explicitly set the value of GL_SPOT_DIRECTION, the light points down the negative z-axis. Also, keep in mind that a spotlight's direction is transformed by the modelview matrix just as though it were a normal vector, and the result is stored in eye coordinates. (See "Controlling a Light's Position and Direction" for more information about such transformations.)

In addition to the spotlight's cutoff angle and direction, there are two ways you can control the intensity distribution of the light within the cone. First, you can set the attenuation factor described earlier, which is multiplied by the light's intensity. You can also set the GL_SPOT_EXPONENT parameter, which by default is zero, to control how concentrated the light is. The light's intensity is highest in the center of the cone. It's attenuated toward the edges of the cone by the cosine of the angle between the direction of the light and the direction from the light to the vertex being lit, raised to the power of the spot exponent. Thus, higher spot exponents result in a more focused light source. (See "The Mathematics of Lighting" for more details on the equations used to calculate light intensity.)

Multiple Lights

As mentioned, you can have at least eight lights in your scene (possibly more, depending on your OpenGL implementation). Since OpenGL needs to perform calculations to determine how much light each vertex receives from each light source, increasing the number of lights adversely affects performance. The constants used to refer to the eight lights are GL_LIGHT0, GL_LIGHT1, GL_LIGHT2, GL_LIGHT3, and so on. In the preceding discussions, parameters related to GL_LIGHT0 were set. If you want an additional light, you need to specify its parameters; also, remember that the default values are different for these other lights than they are for GL_LIGHT0, as explained in Table 5-1. Example 5-3 defines a white attenuated spotlight.

Example 5-3 : Second Light Source

GLfloat light1_ambient[] = { 0.2, 0.2, 0.2, 1.0 };
GLfloat light1_diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat light1_specular[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat light1_position[] = { -2.0, 2.0, 1.0, 1.0 };
GLfloat spot_direction[] = { -1.0, -1.0, 0.0 };

glLightfv(GL_LIGHT1, GL_AMBIENT, light1_ambient);
glLightfv(GL_LIGHT1, GL_DIFFUSE, light1_diffuse);
glLightfv(GL_LIGHT1, GL_SPECULAR, light1_specular);
glLightfv(GL_LIGHT1, GL_POSITION, light1_position);
glLightf(GL_LIGHT1, GL_CONSTANT_ATTENUATION, 1.5);
glLightf(GL_LIGHT1, GL_LINEAR_ATTENUATION, 0.5);
glLightf(GL_LIGHT1, GL_QUADRATIC_ATTENUATION, 0.2);

glLightf(GL_LIGHT1, GL_SPOT_CUTOFF, 45.0);
glLightfv(GL_LIGHT1, GL_SPOT_DIRECTION, spot_direction);
glLightf(GL_LIGHT1, GL_SPOT_EXPONENT, 2.0);

glEnable(GL_LIGHT1);

If these lines were added to Example 5-1, the sphere would be lit with two lights, one directional and one spotlight.

Try This

Modify Example 5-1 in the following manner:

  • Change the first light to be a positional colored light rather than a directional white one.
  • Add an additional colored spotlight. Hint: Use some of the code shown in the preceding section.
  • Measure how these two changes affect performance.

Controlling a Light's Position and Direction

OpenGL treats the position and direction of a light source just as it treats the position of a geometric primitive. In other words, a light source is subject to the same matrix transformations as a primitive. More specifically, when glLight*() is called to specify the position or the direction of a light source, the position or direction is transformed by the current modelview matrix and stored in eye coordinates. This means you can manipulate a light source's position or direction by changing the contents of the modelview matrix. (The projection matrix has no effect on a light's position or direction.) This section explains how to achieve the following three different effects by changing the point in the program at which the light position is set, relative to modeling or viewing transformations:

  • A light position that remains fixed
  • A light that moves around a stationary object
  • A light that moves along with the viewpoint

Keeping the Light Stationary

In the simplest example, as in Example 5-1, the light position remains fixed. To achieve this effect, you need to set the light position after whatever viewing and/or modeling transformation you use. In Example 5-4, the relevant code from the init() and reshape() routines might look like this.

Example 5-4 : Stationary Light Source

glViewport (0, 0, (GLsizei) w, (GLsizei) h);
glMatrixMode (GL_PROJECTION);
glLoadIdentity();
if (w <= h)
    glOrtho (-1.5, 1.5, -1.5*h/w, 1.5*h/w, -10.0, 10.0);
else
    glOrtho (-1.5*w/h, 1.5*w/h, -1.5, 1.5, -10.0, 10.0);
glMatrixMode (GL_MODELVIEW);
glLoadIdentity();

/* later in init() */
GLfloat light_position[] = { 1.0, 1.0, 1.0, 1.0 };
glLightfv(GL_LIGHT0, GL_POSITION, position);

As you can see, the viewport and projection matrices are established first. Then, the identity matrix is loaded as the modelview matrix, after which the light position is set. Since the identity matrix is used, the originally specified light position (1.0, 1.0, 1.0) isn't changed by being multiplied by the modelview matrix. Then, since neither the light position nor the modelview matrix is modified after this point, the direction of the light remains (1.0, 1.0, 1.0).

Independently Moving the Light

Now suppose you want to rotate or translate the light position so that the light moves relative to a stationary object. One way to do this is to set the light position after the modeling transformation, which is itself changed specifically to modify the light position. You can begin with the same series of calls in init() early in the program. Then you need to perform the desired modeling transformation (on the modelview stack) and reset the light position, probably in display(). Example 5-5 shows what display() might be.

Example 5-5 : Independently Moving Light Source

static GLdouble spin;

void display(void)
{
    GLfloat light_position[] = { 0.0, 0.0, 1.5, 1.0 };
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glPushMatrix();
        gluLookAt (0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
        glPushMatrix();
            glRotated(spin, 1.0, 0.0, 0.0);
            glLightfv(GL_LIGHT0, GL_POSITION, light_position);
        glPopMatrix();
        glutSolidTorus (0.275, 0.85, 8, 15);
    glPopMatrix();
    glFlush();
}

spin is a global variable and is probably controlled by an input device. display() causes the scene to be redrawn with the light rotated spin degrees around a stationary torus. Note the two pairs of glPushMatrix() and glPopMatrix() calls, which are used to isolate the viewing and modeling transformations, all of which occur on the modelview stack. Since in Example 5-5 the viewpoint remains constant, the current matrix is pushed down the stack and then the desired viewing transformation is loaded with gluLookAt(). The matrix stack is pushed again before the modeling transformation glRotated() is specified. Then the light position is set in the new, rotated coordinate system so that the light itself appears to be rotated from its previous position. (Remember that the light position is stored in eye coordinates, which are obtained after transformation by the modelview matrix.) After the rotated matrix is popped off the stack, the torus is drawn.

Example 5-6 is a program that rotates a light source around an object. When the left mouse button is pressed, the light position rotates an additional 30 degrees. A small, unlit, wireframe cube is drawn to represent the position of the light in the scene.

Example 5-6 : Moving a Light with Modeling Transformations: movelight.c

#include <GL/gl.h>
#include <GL/glu.h>
#include "glut.h"

static int spin = 0;

void init(void)
{
   glClearColor (0.0, 0.0, 0.0, 0.0);
   glShadeModel (GL_SMOOTH);
   glEnable(GL_LIGHTING);
   glEnable(GL_LIGHT0);
   glEnable(GL_DEPTH_TEST);
}

/*  Here is where the light position is reset after the modeling
 *  transformation (glRotated) is called.  This places the
 *  light at a new position in world coordinates.  The cube
 *  represents the position of the light.
 */
void display(void)
{
   GLfloat position[] = { 0.0, 0.0, 1.5, 1.0 };

   glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   glPushMatrix ();
   glTranslatef (0.0, 0.0, -5.0);

   glPushMatrix ();
   glRotated ((GLdouble) spin, 1.0, 0.0, 0.0);
   glLightfv (GL_LIGHT0, GL_POSITION, position);

   glTranslated (0.0, 0.0, 1.5);
   glDisable (GL_LIGHTING);
   glColor3f (0.0, 1.0, 1.0);
   glutWireCube (0.1);
   glEnable (GL_LIGHTING);
   glPopMatrix ();

   glutSolidTorus (0.275, 0.85, 8, 15);
   glPopMatrix ();
   glFlush ();
}

void reshape (int w, int h)
{
   glViewport (0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode (GL_PROJECTION);
   glLoadIdentity();
   gluPerspective(40.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
}

void mouse(int button, int state, int x, int y)
{
   switch (button) {
      case GLUT_LEFT_BUTTON:
         if (state == GLUT_DOWN) {
            spin = (spin + 30) % 360;
            glutPostRedisplay();
         }
         break;
      default:
         break;
   }
}
int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);
   glutInitWindowSize (500, 500);
   glutInitWindowPosition (100, 100);
   glutCreateWindow (argv[0]);
   init ();
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutMouseFunc(mouse);
   glutMainLoop();
   return 0;
}

Moving the Light Source Together with Your Viewpoint

To create a light that moves along with the viewpoint, you need to set the light position before the viewing transformation. Then the viewing transformation affects both the light and the viewpoint in the same way. Remember that the light position is stored in eye coordinates, and this is one of the few times when eye coordinates are critical. In Example 5-7, the light position is defined in init(), which stores the light position at (0, 0, 0) in eye coordinates. In other words, the light is shining from the lens of the camera.

Example 5-7 : Light Source That Moves with the Viewpoint

GLfloat light_position() = { 0.0, 0.0, 0.0, 1.0 };

glViewport(0, 0, (GLint) w, (GLint) h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(40.0, (GLfloat) w/(GLfloat) h, 1.0, 100.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glLightfv(GL_LIGHT0, GL_POSITION, light_position);

If the viewpoint is now moved, the light will move along with it, maintaining (0, 0, 0) distance, relative to the eye. In the continuation of Example 5-7, which follows next, the global variables (ex, ey, ez) and (upx, upy, upz) control the position of the viewpoint and up vector. The display() routine that's called from the event loop to redraw the scene might be this:

static GLdouble ex, ey, ez, upx, upy, upz;

void display(void)
{
    glClear(GL_COLOR_BUFFER_MASK | GL_DEPTH_BUFFER_MASK);
    glPushMatrix();
        gluLookAt (ex, ey, ez, 0.0, 0.0, 0.0, upx, upy, upz);
        glutSolidTorus (0.275, 0.85, 8, 15);
    glPopMatrix();
    glFlush();
}

When the lit torus is redrawn, both the light position and the viewpoint are moved to the same location. As the values passed to gluLookAt() change and the eye moves, the object will never appear dark, because it is always being illuminated from the eye position. Even though you haven't respecified the light position, the light moves because the eye coordinate system has changed.

This method of moving the light can be very useful for simulating the illumination from a miner's hat. Another example would be carrying a candle or lantern. The light position specified by the call to glLightfv(GL_LIGHTi, GL_POSITION, position) would be the x, y, and z distance from the eye position to the illumination source. Then as the eye position moves, the light will remain the same relative distance away.

Try This

Modify Example 5-6 in the following manner:

  • Make the light translate past the object instead of rotating around it. Hint: Use glTranslated() rather than the first glRotated() in display(), and choose an appropriate value to use instead of spin.
  • Change the attenuation so that the light decreases in intensity as it's moved away from the object. Hint: Add calls to glLight*() to set the desired attenuation parameters.

Selecting a Lighting Model

The OpenGL notion of a lighting model has three components:

  • The global ambient light intensity
  • Whether the viewpoint position is local to the scene or whether it should be considered to be an infinite distance away
  • Whether lighting calculations should be performed differently for both the front and back faces of objects

This section explains how to specify a lighting model. It also discusses how to enable lighting - that is, how to tell OpenGL that you want lighting calculations performed.

The command used to specify all properties of the lighting model is glLightModel*(). glLightModel*() has two arguments: the lighting model property and the desired value for that property.

void glLightModel{if}(GLenum pname, TYPEparam);
void glLightModel{if}v(GLenum pname, TYPE *param);
Sets properties of the lighting model. The characteristic of the lighting model being set is defined by pname, which specifies a named parameter (see Table 5-2). param indicates the values to which the pname characteristic is set; it's a pointer to a group of values if the vector version is used, or the value itself if the nonvector version is used. The nonvector version can be used to set only single-valued lighting model characteristics, not for GL_LIGHT_MODEL_AMBIENT.
Table 5-2 : Default Values for pname Parameter of glLightModel*()
Parameter Name | Default Value | Meaning
GL_LIGHT_MODEL_AMBIENT | (0.2, 0.2, 0.2, 1.0) | ambient RGBA intensity of the entire scene
GL_LIGHT_MODEL_LOCAL_VIEWER | 0.0 or GL_FALSE | how specular reflection angles are computed
GL_LIGHT_MODEL_TWO_SIDE | 0.0 or GL_FALSE | choose between one-sided or two-sided lighting

Global Ambient Light

As discussed earlier, each light source can contribute ambient light to a scene. In addition, there can be other ambient light that's not from any particular source. To specify the RGBA intensity of such global ambient light, use the GL_LIGHT_MODEL_AMBIENT parameter as follows:

GLfloat lmodel_ambient[] = { 0.2, 0.2, 0.2, 1.0 };
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);

In this example, the values used for lmodel_ambient are the default values for GL_LIGHT_MODEL_AMBIENT. Since these numbers yield a small amount of white ambient light, even if you don't add a specific light source to your scene, you can still see the objects in the scene. "Plate 14" in Appendix I shows the effect of different amounts of global ambient light.

Local or Infinite Viewpoint

The location of the viewpoint affects the calculations for highlights produced by specular reflectance. More specifically, the intensity of the highlight at a particular vertex depends on the normal at that vertex, the direction from the vertex to the light source, and the direction from the vertex to the viewpoint. Keep in mind that the viewpoint isn't actually being moved by calls to lighting commands (you need to change the projection transformation, as described in "Projection Transformations" in Chapter 3); instead, different assumptions are made for the lighting calculations as if the viewpoint were moved.

With an infinite viewpoint, the direction between it and any vertex in the scene remains constant. A local viewpoint tends to yield more realistic results, but since the direction has to be calculated for each vertex, overall performance is decreased with a local viewpoint. By default, an infinite viewpoint is assumed. Here's how to change to a local viewpoint:

glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);

This call places the viewpoint at (0, 0, 0) in eye coordinates. To switch back to an infinite viewpoint, pass in GL_FALSE as the argument.

Two-sided Lighting

Lighting calculations are performed for all polygons, whether they're front-facing or back-facing. Since you usually set up lighting conditions with the front-facing polygons in mind, however, the back-facing ones typically aren't correctly illuminated. In Example 5-1 where the object is a sphere, only the front faces are ever seen, since they're the ones on the outside of the sphere. So, in this case, it doesn't matter what the back-facing polygons look like. If the sphere is going to be cut away so that its inside surface will be visible, however, you might want to have the inside surface be fully lit according to the lighting conditions you've defined; you might also want to supply a different material description for the back faces. When you turn on two-sided lighting with

glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);

OpenGL reverses the surface normals for back-facing polygons; typically, this means that the surface normals of visible back- and front-facing polygons face the viewer, rather than pointing away. As a result, all polygons are illuminated correctly. However, these additional operations usually make two-sided lighting perform more slowly than the default one-sided lighting.

To turn two-sided lighting off, pass in GL_FALSE as the argument in the preceding call. (See "Defining Material Properties" for information about how to supply material properties for both faces.) You can also control which faces OpenGL considers to be front-facing with the command glFrontFace(). (See "Reversing and Culling Polygon Faces" in Chapter 2 for more information.)

Enabling Lighting

With OpenGL, you need to explicitly enable (or disable) lighting. If lighting isn't enabled, the current color is simply mapped onto the current vertex, and no calculations concerning normals, light sources, the lighting model, and material properties are performed. Here's how to enable lighting:

glEnable(GL_LIGHTING);

To disable lighting, call glDisable() with GL_LIGHTING as the argument.

You also need to explicitly enable each light source that you define, after you've specified the parameters for that source. Example 5-1 uses only one light, GL_LIGHT0:

glEnable(GL_LIGHT0);

Defining Material Properties

You've seen how to create light sources with certain characteristics and how to define the desired lighting model. This section describes how to define the material properties of the objects in the scene: the ambient, diffuse, and specular colors, the shininess, and the color of any emitted light. (See "The Mathematics of Lighting" for the equations used in the lighting and material-property calculations.) Most of the material properties are conceptually similar to ones you've already used to create light sources. The mechanism for setting them is similar, except that the command used is called glMaterial*().

void glMaterial{if}(GLenum face, GLenum pname, TYPEparam);
void glMaterial{if}v(GLenum face, GLenum pname, TYPE *param);
Specifies a current material property for use in lighting calculations. face can be GL_FRONT, GL_BACK, or GL_FRONT_AND_BACK to indicate which face of the object the material should be applied to. The particular material property being set is identified by pname and the desired values for that property are given by param, which is either a pointer to a group of values (if the vector version is used) or the actual value (if the nonvector version is used). The nonvector version works only for setting GL_SHININESS. The possible values for pname are shown in Table 5-3. Note that GL_AMBIENT_AND_DIFFUSE allows you to set both the ambient and diffuse material colors simultaneously to the same RGBA value.
Table 5-3 : Default Values for pname Parameter of glMaterial*()
Parameter Name | Default Value | Meaning
GL_AMBIENT | (0.2, 0.2, 0.2, 1.0) | ambient color of material
GL_DIFFUSE | (0.8, 0.8, 0.8, 1.0) | diffuse color of material
GL_AMBIENT_AND_DIFFUSE |  | ambient and diffuse color of material
GL_SPECULAR | (0.0, 0.0, 0.0, 1.0) | specular color of material
GL_SHININESS | 0.0 | specular exponent
GL_EMISSION | (0.0, 0.0, 0.0, 1.0) | emissive color of material
GL_COLOR_INDEXES | (0,1,1) | ambient, diffuse, and specular color indices

As discussed in "Selecting a Lighting Model," you can choose to have lighting calculations performed differently for the front- and back-facing polygons of objects. If the back faces might indeed be seen, you can supply different material properties for the front and the back surfaces by using the face parameter of glMaterial*(). See "Plate 14" in Appendix I for an example of an object drawn with different inside and outside material properties.

To give you an idea of the possible effects you can achieve by manipulating material properties, see "Plate 16" in Appendix I. This figure shows the same object drawn with several different sets of material properties. The same light source and lighting model are used for the entire figure. The sections that follow discuss the specific properties used to draw each of these spheres.

Note that most of the material properties set with glMaterial*() are (R, G, B, A) colors. Regardless of what alpha values are supplied for other parameters, the alpha value at any particular vertex is the diffuse-material alpha value (that is, the alpha value given to GL_DIFFUSE with the glMaterial*() command, as described in the next section). (See "Blending" in Chapter 6 for a complete discussion of alpha values.) Also, none of the RGBA material properties apply in color-index mode. (See "Lighting in Color-Index Mode" for more information about what parameters are relevant in color-index mode.)

Diffuse and Ambient Reflection

The GL_DIFFUSE and GL_AMBIENT parameters set with glMaterial*() affect the color of the diffuse and ambient light reflected by an object. Diffuse reflectance plays the most important role in determining what you perceive the color of an object to be. It's affected by the color of the incident diffuse light and the angle of the incident light relative to the normal direction. (It's most intense where the incident light falls perpendicular to the surface.) The position of the viewpoint doesn't affect diffuse reflectance at all.

Ambient reflectance affects the overall color of the object. Because diffuse reflectance is brightest where an object is directly illuminated, ambient reflectance is most noticeable where an object receives no direct illumination. An object's total ambient reflectance is affected by the global ambient light and ambient light from individual light sources. Like diffuse reflectance, ambient reflectance isn't affected by the position of the viewpoint.

For real-world objects, diffuse and ambient reflectance are normally the same color. For this reason, OpenGL provides you with a convenient way of assigning the same value to both simultaneously with glMaterial*():

GLfloat mat_amb_diff[] = { 0.1, 0.5, 0.8, 1.0 };
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE,
            mat_amb_diff);

In this example, the RGBA color (0.1, 0.5, 0.8, 1.0) - a deep blue color - represents the current ambient and diffuse reflectance for both the front- and back-facing polygons.

In "Plate 16" in Appendix I, the first row of spheres has no ambient reflectance (0.0, 0.0, 0.0, 0.0), and the second row has a significant amount of it (0.7, 0.7, 0.7, 1.0).

Specular Reflection

Specular reflection from an object produces highlights. Unlike ambient and diffuse reflection, the amount of specular reflection seen by a viewer does depend on the location of the viewpoint - it's brightest along the direct angle of reflection. To see this, imagine looking at a metallic ball outdoors in the sunlight. As you move your head, the highlight created by the sunlight moves with you to some extent. However, if you move your head too much, you lose the highlight entirely.

OpenGL allows you to set the effect that the material has on reflected light (with GL_SPECULAR) and control the size and brightness of the highlight (with GL_SHININESS). You can assign a number in the range of [0.0, 128.0] to GL_SHININESS - the higher the value, the smaller and brighter (more focused) the highlight. (See "The Mathematics of Lighting" for the details of how specular highlights are calculated.)

In "Plate 16" in Appendix I, the spheres in the first column have no specular reflection. In the second column, GL_SPECULAR and GL_SHININESS are assigned values as follows:

GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat low_shininess[] = { 5.0 };
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_SHININESS, low_shininess);

In the third column, the GL_SHININESS parameter is increased to 100.0.

Emission

By specifying an RGBA color for GL_EMISSION, you can make an object appear to be giving off light of that color. Since most real-world objects (except lights) don't emit light, you'll probably use this feature mostly to simulate lamps and other light sources in a scene. In "Plate 16" in Appendix I, the spheres in the fourth column have a reddish, grey value for GL_EMISSION:

GLfloat mat_emission[] = {0.3, 0.2, 0.2, 0.0};
glMaterialfv(GL_FRONT, GL_EMISSION, mat_emission);

Notice that the spheres appear to be slightly glowing; however, they're not actually acting as light sources. You would need to create a light source and position it at the same location as the sphere to create that effect.

Changing Material Properties

Example 5-1 uses the same material properties for all vertices of the only object in the scene (the sphere). In other situations, you might want to assign different material properties for different vertices on the same object. More likely, you have more than one object in the scene, and each object has different material properties. For example, the code that produced "Plate 16" in Appendix I has to draw twelve different objects (all spheres), each with different material properties. Example 5-8 shows a portion of the code in display().

Example 5-8 : Different Material Properties: material.c

   GLfloat no_mat[] = { 0.0, 0.0, 0.0, 1.0 };
   GLfloat mat_ambient[] = { 0.7, 0.7, 0.7, 1.0 };
   GLfloat mat_ambient_color[] = { 0.8, 0.8, 0.2, 1.0 };
   GLfloat mat_diffuse[] = { 0.1, 0.5, 0.8, 1.0 };
   GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
   GLfloat no_shininess[] = { 0.0 };
   GLfloat low_shininess[] = { 5.0 };
   GLfloat high_shininess[] = { 100.0 };
   GLfloat mat_emission[] = {0.3, 0.2, 0.2, 0.0};

   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

/*  draw sphere in first row, first column
 *  diffuse reflection only; no ambient or specular
 */
   glPushMatrix();
   glTranslatef (-3.75, 3.0, 0.0);
   glMaterialfv(GL_FRONT, GL_AMBIENT, no_mat);
   glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
   glMaterialfv(GL_FRONT, GL_SPECULAR, no_mat);
   glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess);
   glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
   glutSolidSphere(1.0, 16, 16);
   glPopMatrix();

/*  draw sphere in first row, second column
 *  diffuse and specular reflection; low shininess; no ambient
 */
   glPushMatrix();
   glTranslatef (-1.25, 3.0, 0.0);
   glMaterialfv(GL_FRONT, GL_AMBIENT, no_mat);
   glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
   glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
   glMaterialfv(GL_FRONT, GL_SHININESS, low_shininess);
   glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
   glutSolidSphere(1.0, 16, 16);
   glPopMatrix();

/*  draw sphere in first row, third column
 *  diffuse and specular reflection; high shininess; no ambient
 */
   glPushMatrix();
   glTranslatef (1.25, 3.0, 0.0);
   glMaterialfv(GL_FRONT, GL_AMBIENT, no_mat);
   glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
   glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
   glMaterialfv(GL_FRONT, GL_SHININESS, high_shininess);
   glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
   glutSolidSphere(1.0, 16, 16);
   glPopMatrix();

/*  draw sphere in first row, fourth column
 *  diffuse reflection; emission; no ambient or specular refl.
 */
   glPushMatrix();
   glTranslatef (3.75, 3.0, 0.0);
   glMaterialfv(GL_FRONT, GL_AMBIENT, no_mat);
   glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
   glMaterialfv(GL_FRONT, GL_SPECULAR, no_mat);
   glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess);
   glMaterialfv(GL_FRONT, GL_EMISSION, mat_emission);
   glutSolidSphere(1.0, 16, 16);
   glPopMatrix();

As you can see, glMaterialfv() is called repeatedly to set the desired material property for each sphere. Note that it only needs to be called to change a property that needs to be respecified. The second, third, and fourth spheres use the same ambient and diffuse properties as the first sphere, so these properties do not need to be respecified. Since glMaterial*() has a performance cost associated with its use, Example 5-8 could be rewritten to minimize material-property changes.

Another technique for minimizing performance costs associated with changing material properties is to use glColorMaterial().

void glColorMaterial(GLenum face, GLenum mode);
Causes the material property (or properties) specified by mode of the specified material face (or faces) specified by face to track the value of the current color at all times. A change to the current color (using glColor*()) immediately updates the specified material properties. The face parameter can be GL_FRONT, GL_BACK, or GL_FRONT_AND_BACK (the default). The mode parameter can be GL_AMBIENT, GL_DIFFUSE, GL_AMBIENT_AND_DIFFUSE (the default), GL_SPECULAR, or GL_EMISSION. At any given time, only one mode is active. glColorMaterial() has no effect on color-index lighting.

Note that glColorMaterial() specifies two independent values: the first specifies which face or faces are updated, and the second specifies which material property or properties of those faces are updated. OpenGL does not maintain separate mode variables for each face.

After calling glColorMaterial(), you need to call glEnable() with GL_COLOR_MATERIAL as the parameter. Then, you can change the current color using glColor*() (or other material properties, using glMaterial*()) as needed as you draw:

glEnable(GL_COLOR_MATERIAL);
glColorMaterial(GL_FRONT, GL_DIFFUSE);
/* now glColor* changes diffuse reflection  */
glColor3f(0.2, 0.5, 0.8);
/* draw some objects here */
glColorMaterial(GL_FRONT, GL_SPECULAR);
/* glColor* no longer changes diffuse reflection  */
/* now glColor* changes specular reflection  */
glColor3f(0.9, 0.0, 0.2);
/* draw other objects here */
glDisable(GL_COLOR_MATERIAL);

You should use glColorMaterial() whenever you need to change a single material parameter for most vertices in your scene. If you need to change more than one material parameter, as was the case for "Plate 16" in Appendix I, use glMaterial*(). When you don't need the capabilities of glColorMaterial() anymore, be sure to disable it so that you don't get undesired material properties and don't incur the performance cost associated with it. The performance value in using glColorMaterial() varies, depending on your OpenGL implementation. Some implementations may be able to optimize the vertex routines so that they can quickly update material properties based on the current color.

Example 5-9 shows an interactive program that uses glColorMaterial() to change material parameters. Pressing each of the three mouse buttons changes the color of the diffuse reflection.

Example 5-9 : Using glColorMaterial(): colormat.c

#include <GL/gl.h>
#include <GL/glu.h>
#include "glut.h"

GLfloat diffuseMaterial[4] = { 0.5, 0.5, 0.5, 1.0 };

void init(void)
{
   GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
   GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };

   glClearColor (0.0, 0.0, 0.0, 0.0);
   glShadeModel (GL_SMOOTH);
   glEnable(GL_DEPTH_TEST);
   glMaterialfv(GL_FRONT, GL_DIFFUSE, diffuseMaterial);
   glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
   glMaterialf(GL_FRONT, GL_SHININESS, 25.0);
   glLightfv(GL_LIGHT0, GL_POSITION, light_position);
   glEnable(GL_LIGHTING);
   glEnable(GL_LIGHT0);

   glColorMaterial(GL_FRONT, GL_DIFFUSE);
   glEnable(GL_COLOR_MATERIAL);
}

void display(void)
{
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   glutSolidSphere(1.0, 20, 16);
   glFlush ();
}

void reshape (int w, int h)
{
   glViewport (0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode (GL_PROJECTION);
   glLoadIdentity();
   if (w <= h)
      glOrtho (-1.5, 1.5, -1.5*(GLfloat)h/(GLfloat)w,
         1.5*(GLfloat)h/(GLfloat)w, -10.0, 10.0);
   else
      glOrtho (-1.5*(GLfloat)w/(GLfloat)h,
         1.5*(GLfloat)w/(GLfloat)h, -1.5, 1.5, -10.0, 10.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
}

void mouse(int button, int state, int x, int y)
{
   switch (button) {
      case GLUT_LEFT_BUTTON:
         if (state == GLUT_DOWN) {       /*  change red  */
            diffuseMaterial[0] += 0.1;
            if (diffuseMaterial[0] > 1.0)
               diffuseMaterial[0] = 0.0;
            glColor4fv(diffuseMaterial);
            glutPostRedisplay();
         }
         break;
      case GLUT_MIDDLE_BUTTON:
         if (state == GLUT_DOWN) {       /*  change green  */
            diffuseMaterial[1] += 0.1;
            if (diffuseMaterial[1] > 1.0)
               diffuseMaterial[1] = 0.0;
            glColor4fv(diffuseMaterial);
            glutPostRedisplay();
         }
         break;
      case GLUT_RIGHT_BUTTON:
         if (state == GLUT_DOWN) {      /*  change blue  */
            diffuseMaterial[2] += 0.1;
            if (diffuseMaterial[2] > 1.0)
               diffuseMaterial[2] = 0.0;
            glColor4fv(diffuseMaterial);
            glutPostRedisplay();
         }
         break;
      default:
         break;
   }
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);
   glutInitWindowSize (500, 500);
   glutInitWindowPosition (100, 100);
   glutCreateWindow (argv[0]);
   init ();
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutMouseFunc(mouse);
   glutMainLoop();
   return 0;
}

Try This

Modify Example 5-8 in the following manner:

  • Change the global ambient light in the scene. Hint: Alter the value of the GL_LIGHT_MODEL_AMBIENT parameter.
  • Change the diffuse, ambient, and specular reflection parameters, the shininess exponent, and the emission color. Hint: Use the glMaterial*() command, but avoid making excessive calls.
  • Use two-sided materials and add a user-defined clipping plane so that you can see the inside and outside of a row or column of spheres. (See "Additional Clipping Planes" in Chapter 3, if you need to recall user-defined clipping planes.) Hint: Turn on two-sided lighting with GL_LIGHT_MODEL_TWO_SIDE, set the desired material properties, and add a clipping plane.
  • Remove all the glMaterialfv() calls, and use the more efficient glColorMaterial() calls to achieve the same lighting.

The Mathematics of Lighting

Advanced

This section presents the equations used by OpenGL to perform lighting calculations to determine colors when in RGBA mode. (See "The Mathematics of Color-Index Mode Lighting" for corresponding calculations for color-index mode.) You don't need to read this section if you're willing to experiment to obtain the lighting conditions you want. Even after reading this section, you'll probably have to experiment, but you'll have a better idea of how the values of parameters affect a vertex's color. Remember that if lighting is not enabled, the color of a vertex is simply the current color; if it is enabled, the lighting computations described here are carried out in eye coordinates.

In the following equations, mathematical operations are performed separately on the R, G, and B components. Thus, for example, when three terms are shown as added together, the R values, the G values, and the B values for each term are separately added to form the final RGB color (R1+R2+R3, G1+G2+G3, B1+B2+B3). When three terms are multiplied, the calculation is (R1R2R3, G1G2G3, B1B2B3). (Remember that the final A or alpha component at a vertex is equal to the material's diffuse alpha value at that vertex.)

The color produced by lighting a vertex is computed as follows:

vertex color =
the material emission at that vertex +
the global ambient light scaled by the material's ambient property at that vertex +
the ambient, diffuse, and specular contributions from all the light sources, properly attenuated

After lighting calculations are performed, the color values are clamped (in RGBA mode) to the range [0,1].

Note that OpenGL lighting calculations don't take into account the possibility of one object blocking light from another; as a result shadows aren't automatically created. (See "Shadows" in Chapter 14 for a technique to create shadows.) Also keep in mind that with OpenGL, illuminated objects don't radiate light onto other objects.

Material Emission

The material emission term is the simplest. It's the RGB value assigned to the GL_EMISSION parameter.

Scaled Global Ambient Light

The second term is computed by multiplying the global ambient light (as defined by the GL_LIGHT_MODEL_AMBIENT parameter) by the material's ambient property (GL_AMBIENT value as assigned with glMaterial*()):

ambientlight model * ambientmaterial

Each of the R, G, and B values for these two parameters are multiplied separately to compute the final RGB value for this term: (R1R2, G1G2, B1B2).

Contributions from Light Sources

Each light source may contribute to a vertex's color, and these contributions are added together. The equation for computing each light source's contribution is as follows:

contribution = attenuation factor * spotlight effect *

(ambient term + diffuse term + specular term)

Attenuation Factor

The attenuation factor was described in "Position and Attenuation":

where

d = distance between the light's position and the vertex

kc = GL_CONSTANT_ATTENUATION

kl = GL_LINEAR_ATTENUATION

kq = GL_QUADRATIC_ATTENUATION

If the light is a directional one, the attenuation factor is 1.

Spotlight Effect

The spotlight effect evaluates to one of three possible values, depending on whether the light is actually a spotlight and whether the vertex lies inside or outside the cone of illumination produced by the spotlight:

  • 1 if the light isn't a spotlight (GL_SPOT_CUTOFF is 180.0).
  • 0 if the light is a spotlight, but the vertex lies outside the cone of illumination produced by the spotlight.
  • (max {v · d, 0})GL_SPOT_EXPONENT where:

v = (vx, vy, vz) is the unit vector that points from the spotlight (GL_POSITION) to the vertex.

d = (dx, dy, dz) is the spotlight's direction (GL_SPOT_DIRECTION), assuming the light is a spotlight and the vertex lies inside the cone of illumination produced by the spotlight.

The dot product of the two vectors v and d varies as the cosine of the angle between them; hence, objects directly in line get maximum illumination, and objects off the axis have their illumination drop as the cosine of the angle.

To determine whether a particular vertex lies within the cone of illumination, OpenGL evaluates (max {v · d, 0}) where v and d are as defined in the preceding discussion. If this value is less than the cosine of the spotlight's cutoff angle (GL_SPOT_CUTOFF), then the vertex lies outside the cone; otherwise, it's inside the cone.

Ambient Term

The ambient term is simply the ambient color of the light scaled by the ambient material property:

ambientlight *ambientmaterial

Diffuse Term

The diffuse term needs to take into account whether light falls directly on the vertex, the diffuse color of the light, and the diffuse material property:

(max {L · n, 0}) * diffuselight * diffusematerial

where:

L = (Lx, Ly, Lz) is the unit vector that points from the vertex to the light position (GL_POSITION).

n = (nx, ny, nz) is the unit normal vector at the vertex.

Specular Term

The specular term also depends on whether light falls directly on the vertex. If L · n is less than or equal to zero, there is no specular component at the vertex. (If it's less than zero, the light is on the wrong side of the surface.) If there's a specular component, it depends on the following:

  • The unit normal vector at the vertex (nx, ny, nz).
  • The sum of the two unit vectors that point between (1) the vertex and the light position (or light direction) and (2) the vertex and the viewpoint (assuming that GL_LIGHT_MODEL_LOCAL_VIEWER is true; if it's not true, the vector (0, 0, 1) is used as the second vector in the sum). This vector sum is normalized (by dividing each component by the magnitude of the vector) to yield s = (sx, sy, sz).
  • The specular exponent (GL_SHININESS).
  • The specular color of the light (GL_SPECULARlight).
  • The specular property of the material (GL_SPECULARmaterial).

Using these definitions, here's how OpenGL calculates the specular term:

(max {s · n, 0})shininess * specularlight * specularmaterial

However, if L · n = 0, the specular term is 0.

Putting It All Together

Using the definitions of terms described in the preceding paragraphs, the following represents the entire lighting calculation in RGBA mode:

vertex color = emissionmaterial +

ambientlight model * ambientmaterial +

[ambientlight *ambientmaterial +

(max { L · n , 0} ) * diffuselight * diffusematerial +

(max { s · n , 0} )shininess * specularlight * specularmaterial ] i

Lighting in Color-Index Mode

In color-index mode, the parameters comprising RGBA values either have no effect or have a special interpretation. Since it's much harder to achieve certain effects in color-index mode, you should use RGBA whenever possible. In fact, the only light-source, lighting-model, or material parameters in an RGBA form that are used in color-index mode are the light-source parameters GL_DIFFUSE and GL_SPECULAR and the material parameter GL_SHININESS. GL_DIFFUSE and GL_SPECULAR (dl and sl, respectively) are used to compute color-index diffuse and specular light intensities (dci and sci) as follows:

dci = 0.30 R(dl) + 0.59 G(dl) + 0.11 B(dl)

sci = 0.30 R(sl) + 0.59 G(sl) + 0.11 B(sl)

where R(x), G(x), and B(x) refer to the red, green, and blue components, respectively, of color x. The weighting values 0.30, 0.59, and 0.11 reflect the "perceptual" weights that red, green, and blue have for your eye - your eye is most sensitive to green and least sensitive to blue.

To specify material colors in color-index mode, use glMaterial*() with the special parameter GL_COLOR_INDEXES, as follows:

GLfloat mat_colormap[] = { 16.0, 47.0, 79.0 };
glMaterialfv(GL_FRONT, GL_COLOR_INDEXES, mat_colormap);

The three numbers supplied for GL_COLOR_INDEXES specify the color indices for the ambient, diffuse, and specular material colors, respectively. In other words, OpenGL regards the color associated with the first index (16.0 in this example) as the pure ambient color, with the second index (47.0) as the pure diffuse color, and with the third index (79.0) as the pure specular color. (By default, the ambient color index is 0.0, and the diffuse and specular color indices are both 1.0. Note that glColorMaterial() has no effect on color-index lighting.)

As it draws a scene, OpenGL uses colors associated with indices in between these numbers to shade objects in the scene. Therefore, you must build a color ramp between the indicated indices (in this example, between indices 16 and 47, and then between 47 and 79). Often, the color ramp is built smoothly, but you might want to use other formulations to achieve different effects. Here's an example of a smooth color ramp that starts with a black ambient color and goes through a magenta diffuse color to a white specular color:

for (i = 0; i < 32; i++) {
   glutSetColor (16 + i, 1.0 * (i/32.0), 0.0, 1.0 * (i/32.0));
   glutSetColor (48 + i, 1.0, 1.0 * (i/32.0), 1.0);
}

The GLUT library command glutSetColor() takes four arguments. It associates the color index indicated by the first argument to the RGB triplet specified by the last three arguments. When i = 0, the color index 16 is assigned the RGB value (0.0, 0.0, 0.0), or black. The color ramp builds smoothly up to the diffuse material color at index 47 (when i = 31), which is assigned the pure magenta RGB value (1.0, 0.0, 1.0). The second loop builds the ramp between the magenta diffuse color and the white (1.0, 1.0, 1.0) specular color (index 79). "Plate 15" in Appendix I shows the result of using this color ramp with a single lit sphere.

The Mathematics of Color-Index Mode Lighting

Advanced

As you might expect, since the allowable parameters are different for color-index mode than for RGBA mode, the calculations are different as well. Since there's no material emission and no ambient light, the only terms of interest from the RGBA equations are the diffuse and specular contributions from the light sources and the shininess. Even these need to be modified, however, as explained next.

Begin with the diffuse and specular terms from the RGBA equations. In the diffuse term, instead of diffuselight * diffusematerial, substitute dci as defined in the previous section for color-index mode. Similarly, in the specular term, instead of specularlight * specularmaterial, use sci as defined in the previous section. (Calculate the attenuation, spotlight effect, and all other components of these terms as before.) Call these modified diffuse and specular terms d and s, respectively. Now let s' = min{ s, 1 }, and then compute

c = am + d(1-s')(dm-am) + s'(sm-am)

where am, dm, and sm are the ambient, diffuse, and specular material indexes specified using GL_COLOR_INDEXES. The final color index is

c' = min { c, sm }

After lighting calculations are performed, the color-index values are converted to fixed-point (with an unspecified number of bits to the right of the binary point). Then the integer portion is masked (bitwise ANDed) with 2n-1, where n is the number of bits in a color in the color-index buffer.


Chapter 6 - OpenGL Programming Guide

OpenGL Programming Guide
Programming Guide > Chapter 6

Chapter 6
Blending, Antialiasing, Fog, and Polygon Offset

Chapter Objectives

After reading this chapter, you'll be able to do the following:

  • Blend colors to achieve such effects as making objects appear translucent
  • Smooth jagged edges of lines and polygons with antialiasing
  • Create scenes with realistic atmospheric effects
  • Draw geometry at or near the same depth, but avoid unaesthetic artifacts from intersecting geometry

The preceding chapters have given you the basic information you need to create a computer-graphics scene; you've learned how to do the following:

  • Draw geometric shapes
  • Transform those geometric shapes so that they can be viewed from whatever perspective you wish
  • Specify how the geometric shapes in your scene should be colored and shaded
  • Add lights and indicate how they should affect the shapes in your scene

Now you're ready to get a little fancier. This chapter discusses four techniques that can add extra detail and polish to your scene. None of these techniques is hard to use - in fact, it's probably harder to explain them than to use them. Each of these techniques is described in its own major section:

  • "Blending" tells you how to specify a blending function that combines color values from a source and a destination. The final effect is that parts of your scene appear translucent.
  • "Antialiasing" explains this relatively subtle technique that alters colors so that the edges of points, lines, and polygons appear smooth rather than angular and jagged.
  • "Fog" describes how to create the illusion of depth by computing the color values of an object based on its distance from the viewpoint. Thus, objects that are far away appear to fade into the background, just as they do in real life.
  • If you've tried to draw a wireframe outline atop a shaded object and used the same vertices, you've probably noticed some ugly visual artifacts. "Polygon Offset" shows you how to tweak (offset) depth values to make an outlined, shaded object look beautiful.

Blending

You've already seen alpha values (alpha is the A in RGBA), but they've been ignored until now. Alpha values are specified with glColor*(), when using glClearColor() to specify a clearing color and when specifying certain lighting parameters such as a material property or light-source intensity. As you learned in Chapter 4, the pixels on a monitor screen emit red, green, and blue light, which is controlled by the red, green, and blue color values. So how does an alpha value affect what gets drawn in a window on the screen?

When blending is enabled, the alpha value is often used to combine the color value of the fragment being processed with that of the pixel already stored in the framebuffer. Blending occurs after your scene has been rasterized and converted to fragments, but just before the final pixels are drawn in the framebuffer. Alpha values can also be used in the alpha test to accept or reject a fragment based on its alpha value. (See Chapter 10 for more information about this process.)

Without blending, each new fragment overwrites any existing color values in the framebuffer, as though the fragment were opaque. With blending, you can control how (and how much of) the existing color value should be combined with the new fragment's value. Thus you can use alpha blending to create a translucent fragment that lets some of the previously stored color value "show through." Color blending lies at the heart of techniques such as transparency, digital compositing, and painting.

Note: Alpha values aren't specified in color-index mode, so blending operations aren't performed in color-index mode.

The most natural way to think of blending operations is to think of the RGB components of a fragment as representing its color and the alpha component as representing opacity. Transparent or translucent surfaces have lower opacity than opaque ones and, therefore, lower alpha values. For example, if you're viewing an object through green glass, the color you see is partly green from the glass and partly the color of the object. The percentage varies depending on the transmission properties of the glass: If the glass transmits 80 percent of the light that strikes it (that is, has an opacity of 20 percent), the color you see is a combination of 20 percent glass color and 80 percent of the color of the object behind it. You can easily imagine situations with multiple translucent surfaces. If you look at an automobile, for instance, its interior has one piece of glass between it and your viewpoint; some objects behind the automobile are visible through two pieces of glass.

The Source and Destination Factors

During blending, color values of the incoming fragment (the source) are combined with the color values of the corresponding currently stored pixel (the destination) in a two-stage process. First you specify how to compute source and destination factors. These factors are RGBA quadruplets that are multiplied by each component of the R, G, B, and A values in the source and destination, respectively. Then the corresponding components in the two sets of RGBA quadruplets are added. To show this mathematically, let the source and destination blending factors be (Sr, Sg, Sb, Sa) and (Dr, Dg, Db, Da), respectively, and the RGBA values of the source and destination be indicated with a subscript of s or d. Then the final, blended RGBA values are given by

(RsSr+RdDr, GsSg+GdDg, BsSb+BdDb, AsSa+AdDa)

Each component of this quadruplet is eventually clamped to [0,1].

Now consider how the source and destination blending factors are generated. You use glBlendFunc() to supply two constants: one that specifies how the source factor should be computed and one that indicates how the destination factor should be computed. To have blending take effect, you also need to enable it:

glEnable(GL_BLEND);

Use glDisable() with GL_BLEND to disable blending. Also note that using the constants GL_ONE (source) and GL_ZERO (destination) gives the same results as when blending is disabled; these values are the default.

void glBlendFunc(GLenum sfactor, GLenum dfactor);
Controls how color values in the fragment being processed (the source) are combined with those already stored in the framebuffer (the destination). The argument sfactor indicates how to compute a source blending factor; dfactor indicates how to compute a destination blending factor. The possible values for these arguments are explained in Table 6-1. The blend factors are assumed to lie in the range [0,1]; after the color values in the source and destination are combined, they're clamped to the range [0,1].

Note: In Table 6-1, the RGBA values of the source and destination are indicated with the subscripts s and d, respectively. Subtraction of quadruplets means subtracting them componentwise. The Relevant Factor column indicates whether the corresponding constant can be used to specify the source or destination blend factor.

Table 6-1 : Source and Destination Blending Factors

Constant | Relevant Factor | Computed Blend Factor
GL_ZERO | source or destination | (0, 0, 0, 0)
GL_ONE | source or destination | (1, 1, 1, 1)
GL_DST_COLOR | source | (Rd, Gd, Bd, Ad)
GL_SRC_COLOR | destination | (Rs, Gs, Bs, As)
GL_ONE_MINUS_DST_COLOR | source | (1, 1, 1, 1)-(Rd, Gd, Bd, Ad)
GL_ONE_MINUS_SRC_COLOR | destination | (1, 1, 1, 1)-(Rs, Gs, Bs, As)
GL_SRC_ALPHA | source or destination | (As, As, As, As)
GL_ONE_MINUS_SRC_ALPHA | source or destination | (1, 1, 1, 1)-(As, As, As, As)
GL_DST_ALPHA | source or destination | (Ad, Ad, Ad, Ad)
GL_ONE_MINUS_DST_ALPHA | source or destination | (1, 1, 1, 1)-(Ad, Ad, Ad, Ad)
GL_SRC_ALPHA_SATURATE | source | (f, f, f, 1); f=min(As, 1-Ad)

Sample Uses of Blending

Not all combinations of source and destination factors make sense. Most applications use a small number of combinations. The following paragraphs describe typical uses for particular combinations of source and destination factors. Some of these examples use only the incoming alpha value, so they work even when alpha values aren't stored in the framebuffer. Also note that often there's more than one way to achieve some of these effects.

  • One way to draw a picture composed half of one image and half of another, equally blended, is to set the source factor to GL_ONE and the destination factor to GL_ZERO, and draw the first image. Then set the source factor to GL_SRC_ALPHA and destination factor to GL_ONE_MINUS_SRC_ALPHA, and draw the second image with alpha equal to 0.5. This pair of factors probably represents the most commonly used blending operation. If the picture is supposed to be blended with 0.75 of the first image and 0.25 of the second, draw the first image as before, and draw the second with an alpha of 0.25.
  • To blend three different images equally, set the destination factor to GL_ONE and the source factor to GL_SRC_ALPHA. Draw each of the images with an alpha equal to 0.3333333. With this technique, each image is only one-third of its original brightness, which is noticeable where the images don't overlap.
  • Suppose you're writing a paint program, and you want to have a brush that gradually adds color so that each brush stroke blends in a little more color with whatever is currently in the image (say 10 percent color with 90 percent image on each pass). To do this, draw the image of the brush with alpha of 10 percent and use GL_SRC_ALPHA (source) and GL_ONE_MINUS_SRC_ALPHA (destination). Note that you can vary the alphas across the brush to make the brush add more of its color in the middle and less on the edges, for an antialiased brush shape. (See "Antialiasing.") Similarly, erasers can be implemented by setting the eraser color to the background color.
  • The blending functions that use the source or destination colors - GL_DST_COLOR or GL_ONE_MINUS_DST_COLOR for the source factor and GL_SRC_COLOR or GL_ONE_MINUS_SRC_COLOR for the destination factor - effectively allow you to modulate each color component individually. This operation is equivalent to applying a simple filter - for example, multiplying the red component by 80 percent, the green component by 40 percent, and the blue component by 72 percent would simulate viewing the scene through a photographic filter that blocks 20 percent of red light, 60 percent of green, and 28 percent of blue.
  • Suppose you want to draw a picture composed of three translucent surfaces, some obscuring others, and all over a solid background. Assume the farthest surface transmits 80 percent of the color behind it, the next transmits 40 percent, and the closest transmits 90 percent. To compose this picture, draw the background first with the default source and destination factors, and then change the blending factors to GL_SRC_ALPHA (source) and GL_ONE_MINUS_SRC_ALPHA (destination). Next, draw the farthest surface with an alpha of 0.2, then the middle surface with an alpha of 0.6, and finally the closest surface with an alpha of 0.1.
  • If your system has alpha planes, you can render objects one at a time (including their alpha values), read them back, and then perform interesting matting or compositing operations with the fully rendered objects. (See "Compositing 3D Rendered Images" by Tom Duff, SIGGRAPH 1985 Proceedings, p. 4144, for examples of this technique.) Note that objects used for picture composition can come from any source - they can be rendered using OpenGL commands, rendered using techniques such as ray-tracing or radiosity that are implemented in another graphics library, or obtained by scanning in existing images.
  • You can create the effect of a nonrectangular raster image by assigning different alpha values to individual fragments in the image. In most cases, you would assign an alpha of 0 to each "invisible" fragment and an alpha of 1.0 to each opaque fragment. For example, you can draw a polygon in the shape of a tree and apply a texture map of foliage; the viewer can see through parts of the rectangular texture that aren't part of the tree if you've assigned them alpha values of 0. This method, sometimes called billboarding, is much faster than creating the tree out of three-dimensional polygons. An example of this technique is shown in Figure 6-1: The tree is a single rectangular polygon that can be rotated about the center of the trunk, as shown by the outlines, so that it's always facing the viewer. (See "Texture Functions" in Chapter 9 for more information about blending textures.)

Figure 6-1 : Creating a Nonrectangular Raster Image

  • Blending is also used for antialiasing, which is a rendering technique to reduce the jagged appearance of primitives drawn on a raster screen. (See "Antialiasing" for more information.)

A Blending Example

Example 6-1 draws two overlapping colored triangles, each with an alpha of 0.75. Blending is enabled and the source and destination blending factors are set to GL_SRC_ALPHA and GL_ONE_MINUS_SRC_ALPHA, respectively.

When the program starts up, a yellow triangle is drawn on the left and then a cyan triangle is drawn on the right so that in the center of the window, where the triangles overlap, cyan is blended with the original yellow. You can change which triangle is drawn first by typing `t' in the window.

Example 6-1 : Blending Example: alpha.c

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <stdlib.h>

static int leftFirst = GL_TRUE;

/*  Initialize alpha blending function.  */
static void init(void)
{
   glEnable (GL_BLEND);
   glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
   glShadeModel (GL_FLAT);
   glClearColor (0.0, 0.0, 0.0, 0.0);
}

static void drawLeftTriangle(void)
{
/* draw yellow triangle on LHS of screen */
   glBegin (GL_TRIANGLES);
      glColor4f(1.0, 1.0, 0.0, 0.75);
      glVertex3f(0.1, 0.9, 0.0);
      glVertex3f(0.1, 0.1, 0.0);
      glVertex3f(0.7, 0.5, 0.0);
   glEnd();
}

static void drawRightTriangle(void)
{
/* draw cyan triangle on RHS of screen */
   glBegin (GL_TRIANGLES);
      glColor4f(0.0, 1.0, 1.0, 0.75);
      glVertex3f(0.9, 0.9, 0.0);
      glVertex3f(0.3, 0.5, 0.0);
      glVertex3f(0.9, 0.1, 0.0);
   glEnd();
}

void display(void)
{
   glClear(GL_COLOR_BUFFER_BIT);

   if (leftFirst) {
      drawLeftTriangle();
      drawRightTriangle();
   }
   else {
      drawRightTriangle();
      drawLeftTriangle();
   }
   glFlush();
}

void reshape(int w, int h)
{
   glViewport(0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   if (w <= h)
      gluOrtho2D (0.0, 1.0, 0.0, 1.0*(GLfloat)h/(GLfloat)w);
   else
      gluOrtho2D (0.0, 1.0*(GLfloat)w/(GLfloat)h, 0.0, 1.0);
}

void keyboard(unsigned char key, int x, int y)
{
   switch (key) {
      case `t':
      case `T':
         leftFirst = !leftFirst;
         glutPostRedisplay();
         break;
      case 27:  /*  Escape key  */
         exit(0);
         break;
      default:
         break;
   }
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
   glutInitWindowSize (200, 200);
   glutCreateWindow (argv[0]);
   init();
   glutReshapeFunc (reshape);
   glutKeyboardFunc (keyboard);
   glutDisplayFunc (display);
   glutMainLoop();
   return 0;
}

The order in which the triangles are drawn affects the color of the overlapping region. When the left triangle is drawn first, cyan fragments (the source) are blended with yellow fragments, which are already in the framebuffer (the destination). When the right triangle is drawn first, yellow is blended with cyan. Because the alpha values are all 0.75, the actual blending factors become 0.75 for the source and 1.0 - 0.75 = 0.25 for the destination. In other words, the source fragments are somewhat translucent, but they have more effect on the final color than the destination fragments.

Three-Dimensional Blending with the Depth Buffer

As you saw in the previous example, the order in which polygons are drawn greatly affects the blended result. When drawing three-dimensional translucent objects, you can get different appearances depending on whether you draw the polygons from back to front or from front to back. You also need to consider the effect of the depth buffer when determining the correct order. (See "A Hidden-Surface Removal Survival Kit" in Chapter 5 for an introduction to the depth buffer. Also see "Depth Test" in Chapter 10 for more information.) The depth buffer keeps track of the distance between the viewpoint and the portion of the object occupying a given pixel in a window on the screen; when another candidate color arrives for that pixel, it's drawn only if its object is closer to the viewpoint, in which case its depth value is stored in the depth buffer. With this method, obscured (or hidden) portions of surfaces aren't necessarily drawn and therefore aren't used for blending.

If you want to render both opaque and translucent objects in the same scene, then you want to use the depth buffer to perform hidden-surface removal for any objects that lie behind the opaque objects. If an opaque object hides either a translucent object or another opaque object, you want the depth buffer to eliminate the more distant object. If the translucent object is closer, however, you want to blend it with the opaque object. You can generally figure out the correct order to draw the polygons if everything in the scene is stationary, but the problem can quickly become too hard if either the viewpoint or the object is moving.

The solution is to enable depth buffering but make the depth buffer read-only while drawing the translucent objects. First you draw all the opaque objects, with the depth buffer in normal operation. Then you preserve these depth values by making the depth buffer read-only. When the translucent objects are drawn, their depth values are still compared to the values established by the opaque objects, so they aren't drawn if they're behind the opaque ones. If they're closer to the viewpoint, however, they don't eliminate the opaque objects, since the depth-buffer values can't change. Instead, they're blended with the opaque objects. To control whether the depth buffer is writable, use glDepthMask(); if you pass GL_FALSE as the argument, the buffer becomes read-only, whereas GL_TRUE restores the normal, writable operation.

Example 6-2 demonstrates how to use this method to draw opaque and translucent three-dimensional objects. In the program, typing `a' triggers an animation sequence in which a translucent cube moves through an opaque sphere. Pressing the `r' key resets the objects in the scene to their initial positions. To get the best results when transparent objects overlap, draw the objects from back to front.

Example 6-2 : Three-Dimensional Blending: alpha3D.c

#include <stdlib.h>
#include <stdio.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>

#define MAXZ 8.0
#define MINZ -8.0
#define ZINC 0.4
static float solidZ = MAXZ;
static float transparentZ = MINZ;
static GLuint sphereList, cubeList;

static void init(void)
{
   GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 0.15 };
   GLfloat mat_shininess[] = { 100.0 };
   GLfloat position[] = { 0.5, 0.5, 1.0, 0.0 };

   glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
   glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
   glLightfv(GL_LIGHT0, GL_POSITION, position);

   glEnable(GL_LIGHTING);
   glEnable(GL_LIGHT0);
   glEnable(GL_DEPTH_TEST);

   sphereList = glGenLists(1);
   glNewList(sphereList, GL_COMPILE);
      glutSolidSphere (0.4, 16, 16);
   glEndList();

   cubeList = glGenLists(1);
   glNewList(cubeList, GL_COMPILE);
      glutSolidCube (0.6);
   glEndList();
}

void display(void)
{
   GLfloat mat_solid[] = { 0.75, 0.75, 0.0, 1.0 };
   GLfloat mat_zero[] = { 0.0, 0.0, 0.0, 1.0 };
   GLfloat mat_transparent[] = { 0.0, 0.8, 0.8, 0.6 };
   GLfloat mat_emission[] = { 0.0, 0.3, 0.3, 0.6 };

   glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

   glPushMatrix ();
      glTranslatef (-0.15, -0.15, solidZ);
      glMaterialfv(GL_FRONT, GL_EMISSION, mat_zero);
      glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_solid);
      glCallList (sphereList);
   glPopMatrix ();

   glPushMatrix ();
      glTranslatef (0.15, 0.15, transparentZ);
      glRotatef (15.0, 1.0, 1.0, 0.0);
      glRotatef (30.0, 0.0, 1.0, 0.0);
      glMaterialfv(GL_FRONT, GL_EMISSION, mat_emission);
      glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_transparent);
      glEnable (GL_BLEND);
      glDepthMask (GL_FALSE);
      glBlendFunc (GL_SRC_ALPHA, GL_ONE);
      glCallList (cubeList);
      glDepthMask (GL_TRUE);
      glDisable (GL_BLEND);
   glPopMatrix ();

   glutSwapBuffers();
}

void reshape(int w, int h)
{
   glViewport(0, 0, (GLint) w, (GLint) h);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   if (w <= h)
      glOrtho (-1.5, 1.5, -1.5*(GLfloat)h/(GLfloat)w,
             1.5*(GLfloat)h/(GLfloat)w, -10.0, 10.0);
   else
      glOrtho (-1.5*(GLfloat)w/(GLfloat)h,
             1.5*(GLfloat)w/(GLfloat)h, -1.5, 1.5, -10.0, 10.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
}

void animate(void)
{
   if (solidZ <= MINZ || transparentZ >= MAXZ)
      glutIdleFunc(NULL);
   else {
      solidZ -= ZINC;
      transparentZ += ZINC;
      glutPostRedisplay();
   }
}

void keyboard(unsigned char key, int x, int y)
{
   switch (key) {
      case `a':
      case `A':
         solidZ = MAXZ;
         transparentZ = MINZ;
         glutIdleFunc(animate);
         break;
      case `r':
      case `R':
         solidZ = MAXZ;
         transparentZ = MINZ;
         glutPostRedisplay();
         break;
      case 27:
        exit(0);
    }
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);
   glutInitWindowSize(500, 500);
   glutCreateWindow(argv[0]);
   init();
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyboard);
   glutDisplayFunc(display);
   glutMainLoop();
   return 0;
}

Antialiasing

You might have noticed in some of your OpenGL pictures that lines, especially nearly horizontal or nearly vertical ones, appear jagged. These jaggies appear because the ideal line is approximated by a series of pixels that must lie on the pixel grid. The jaggedness is called aliasing, and this section describes antialiasing techniques to reduce it. Figure 6-2 shows two intersecting lines, both aliased and antialiased. The pictures have been magnified to show the effect.

Figure 6-2 : Aliased and Antialiased Lines

Figure 6-3 shows how a diagonal line 1 pixel wide covers more of some pixel squares than others. In fact, when performing antialiasing, OpenGL calculates a coverage value for each fragment based on the fraction of the pixel square on the screen that it would cover. The figure shows these coverage values for the line. In RGBA mode, OpenGL multiplies the fragment's alpha value by its coverage. You can then use the resulting alpha value to blend the fragment with the corresponding pixel already in the framebuffer. In color-index mode, OpenGL sets the least significant 4 bits of the color index based on the fragment's coverage (0000 for no coverage and 1111 for complete coverage). It's up to you to load your color map and apply it appropriately to take advantage of this coverage information.

Figure 6-3 : Determining Coverage Values

The details of calculating coverage values are complex, difficult to specify in general, and in fact may vary slightly depending on your particular implementation of OpenGL. You can use the glHint() command to exercise some control over the trade-off between image quality and speed, but not all implementations will take the hint.

void glHint(GLenum target, GLenum hint);
Controls certain aspects of OpenGL behavior. The target parameter indicates which behavior is to be controlled; its possible values are shown in Table 6-2. The hint parameter can be GL_FASTEST to indicate that the most efficient option should be chosen, GL_NICEST to indicate the highest-quality option, or GL_DONT_CARE to indicate no preference. The interpretation of hints is implementation-dependent; an implementation can ignore them entirely. (For more information about the relevant topics, see "Antialiasing" for the details on sampling and "Fog" for details on fog.)
The GL_PERSPECTIVE_CORRECTION_HINT target parameter refers to how color values and texture coordinates are interpolated across a primitive: either linearly in screen space (a relatively simple calculation) or in a perspective-correct manner (which requires more computation). Often, systems perform linear color interpolation because the results, while not technically correct, are visually acceptable; however, in most cases textures require perspective-correct interpolation to be visually acceptable. Thus, an implementation can choose to use this parameter to control the method used for interpolation. (See Chapter 3 for a discussion of perspective projection, Chapter 4 for a discussion of color, and Chapter 9 for a discussion of texture mapping.)
Table 6-2 : Values for Use with glHint()
Parameter | Meaning
GL_POINT_SMOOTH_HINT, GL_LINE_SMOOTH_HINT, GL_POLYGON_SMOOTH_HINT | Specify the desired sampling quality of points, lines, or polygons during antialiasing operations
GL_FOG_HINT | Specifies whether fog calculations are done per pixel (GL_NICEST) or per vertex (GL_FASTEST)
GL_PERSPECTIVE_CORRECTION_HINT | Specifies the desired quality of color and texture-coordinate interpolation

Antialiasing Points or Lines

To antialias points or lines, you need to turn on antialiasing with glEnable(), passing in GL_POINT_SMOOTH or GL_LINE_SMOOTH, as appropriate. You might also want to provide a quality hint with glHint(). (Remember that you can set the size of a point or the width of a line. You can also stipple a line. See "Line Details" in Chapter 2.) Next follow the procedures described in one of the following sections, depending on whether you're in RGBA or color-index mode.

Antialiasing in RGBA Mode

In RGBA mode, you need to enable blending. The blending factors you most likely want to use are GL_SRC_ALPHA (source) and GL_ONE_MINUS_SRC_ALPHA (destination). Alternatively, you can use GL_ONE for the destination factor to make lines a little brighter where they intersect. Now you're ready to draw whatever points or lines you want antialiased. The antialiased effect is most noticeable if you use a fairly high alpha value. Remember that since you're performing blending, you might need to consider the rendering order as described in "Three-Dimensional Blending with the Depth Buffer." However, in most cases, the ordering can be ignored without significant adverse effects. Example 6-3 initializes the necessary modes for antialiasing and then draws two intersecting diagonal lines. When you run this program, press the `r' key to rotate the lines so that you can see the effect of antialiasing on lines of different slopes. Note that the depth buffer isn't enabled in this example.

Example 6-3 : Antialiased lines: aargb.c

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <stdlib.h>
#include <stdio.h>

static float rotAngle = 0.;

/*  Initialize antialiasing for RGBA mode, including alpha
 *  blending, hint, and line width.  Print out implementation
 *  specific info on line width granularity and width.
 */
void init(void)
{
   GLfloat values[2];
   glGetFloatv (GL_LINE_WIDTH_GRANULARITY, values);
   printf ("GL_LINE_WIDTH_GRANULARITY value is %3.1f\n",
      values[0]);
   glGetFloatv (GL_LINE_WIDTH_RANGE, values);
   printf ("GL_LINE_WIDTH_RANGE values are %3.1f %3.1f\n",
      values[0], values[1]);

   glEnable (GL_LINE_SMOOTH);
   glEnable (GL_BLEND);
   glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
   glHint (GL_LINE_SMOOTH_HINT, GL_DONT_CARE);
   glLineWidth (1.5);

   glClearColor(0.0, 0.0, 0.0, 0.0);
}

/* Draw 2 diagonal lines to form an X */
void display(void)
{
   glClear(GL_COLOR_BUFFER_BIT);

   glColor3f (0.0, 1.0, 0.0);
   glPushMatrix();
   glRotatef(-rotAngle, 0.0, 0.0, 0.1);
   glBegin (GL_LINES);
      glVertex2f (-0.5, 0.5);
      glVertex2f (0.5, -0.5);
   glEnd ();
   glPopMatrix();

   glColor3f (0.0, 0.0, 1.0);
   glPushMatrix();
   glRotatef(rotAngle, 0.0, 0.0, 0.1);
   glBegin (GL_LINES);
      glVertex2f (0.5, 0.5);
      glVertex2f (-0.5, -0.5);
   glEnd ();
   glPopMatrix();

   glFlush();
}
void reshape(int w, int h)
{
   glViewport(0, 0, (GLint) w, (GLint) h);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   if (w <= h)
      gluOrtho2D (-1.0, 1.0,
         -1.0*(GLfloat)h/(GLfloat)w, 1.0*(GLfloat)h/(GLfloat)w);
   else
      gluOrtho2D (-1.0*(GLfloat)w/(GLfloat)h,
         1.0*(GLfloat)w/(GLfloat)h, -1.0, 1.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
}

void keyboard(unsigned char key, int x, int y)
{
   switch (key) {
      case `r':
      case `R':
         rotAngle += 20.;
         if (rotAngle >= 360.) rotAngle = 0.;
         glutPostRedisplay();
         break;
      case 27:  /*  Escape Key  */
         exit(0);
         break;
      default:
         break;
    }
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
   glutInitWindowSize (200, 200);
   glutCreateWindow (argv[0]);
   init();
   glutReshapeFunc (reshape);
   glutKeyboardFunc (keyboard);
   glutDisplayFunc (display);
   glutMainLoop();
   return 0;
}

Antialiasing in Color-Index Mode

The tricky part about antialiasing in color-index mode is loading and using the color map. Since the last 4 bits of the color index indicate the coverage value, you need to load sixteen contiguous indices with a color ramp from the background color to the object's color. (The ramp has to start with an index value that's a multiple of 16.) Then you clear the color buffer to the first of the sixteen colors in the ramp and draw your points or lines using colors in the ramp. Example 6-4 demonstrates how to construct the color ramp to draw antialiased lines in color-index mode. In this example, two color ramps are created: one contains shades of green and the other shades of blue.

Example 6-4 : Antialiasing in Color-Index Mode: aaindex.c

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <stdlib.h>

#define RAMPSIZE 16
#define RAMP1START 32
#define RAMP2START 48

static float rotAngle = 0.;

/*  Initialize antialiasing for color-index mode,
 *  including loading a green color ramp starting
 *  at RAMP1START, and a blue color ramp starting
 *  at RAMP2START. The ramps must be a multiple of 16.
 */
void init(void)
{
   int i;

   for (i = 0; i < RAMPSIZE; i++) {
      GLfloat shade;
      shade = (GLfloat) i/(GLfloat) RAMPSIZE;
      glutSetColor(RAMP1START+(GLint)i, 0., shade, 0.);
      glutSetColor(RAMP2START+(GLint)i, 0., 0., shade);
   }
   glEnable (GL_LINE_SMOOTH);
   glHint (GL_LINE_SMOOTH_HINT, GL_DONT_CARE);
   glLineWidth (1.5);

   glClearIndex ((GLfloat) RAMP1START);
}
/*  Draw 2 diagonal lines to form an X */
void display(void)
{
   glClear(GL_COLOR_BUFFER_BIT);

   glIndexi(RAMP1START);
   glPushMatrix();
   glRotatef(-rotAngle, 0.0, 0.0, 0.1);
   glBegin (GL_LINES);
      glVertex2f (-0.5, 0.5);
      glVertex2f (0.5, -0.5);
   glEnd ();
   glPopMatrix();

   glIndexi(RAMP2START);
   glPushMatrix();
   glRotatef(rotAngle, 0.0, 0.0, 0.1);
   glBegin (GL_LINES);
      glVertex2f (0.5, 0.5);
      glVertex2f (-0.5, -0.5);
   glEnd ();
   glPopMatrix();

   glFlush();
}

void reshape(int w, int h)
{
   glViewport(0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   if (w <= h)
      gluOrtho2D (-1.0, 1.0,
         -1.0*(GLfloat)h/(GLfloat)w, 1.0*(GLfloat)h/(GLfloat)w);
   else
      gluOrtho2D (-1.0*(GLfloat)w/(GLfloat)h,
         1.0*(GLfloat)w/(GLfloat)h, -1.0, 1.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
}
void keyboard(unsigned char key, int x, int y)
{
   switch (key) {
      case `r':
      case `R':
         rotAngle += 20.;
         if (rotAngle >= 360.) rotAngle = 0.;
         glutPostRedisplay();
         break;
      case 27:  /*  Escape Key */
         exit(0);
         break;
      default:
         break;
    }
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_SINGLE | GLUT_INDEX);
   glutInitWindowSize (200, 200);
   glutCreateWindow (argv[0]);
   init();
   glutReshapeFunc (reshape);
   glutKeyboardFunc (keyboard);
   glutDisplayFunc (display);
   glutMainLoop();
   return 0;
}

Since the color ramp goes from the background color to the object's color, the antialiased lines look correct only in the areas where they are drawn on top of the background. When the blue line is drawn, it erases part of the green line at the point where the lines intersect. To fix this, you would need to redraw the area where the lines intersect using a ramp that goes from green (the color of the line in the framebuffer) to blue (the color of the line being drawn). However, this requires additional calculations and it is usually not worth the effort since the intersection area is small. Note that this is not a problem in RGBA mode, since the colors of object being drawn are blended with the color already in the framebuffer.

You may also want to enable the depth test when drawing antialiased points and lines in color-index mode. In this example, the depth test is disabled since both of the lines lie in the same z-plane. However, if you want to draw a three-dimensional scene, you should enable the depth buffer so that the resulting pixel colors correspond to the "nearest" objects.

The trick described in "Three-Dimensional Blending with the Depth Buffer" can also be used to mix antialiased points and lines with aliased, depth-buffered polygons. To do this, draw the polygons first, then make the depth buffer read-only and draw the points and lines. The points and lines intersect nicely with each other but will be obscured by nearer polygons.

Try This

Take a previous program, such as the robot arm or solar system examples described in "Examples of Composing Several Transformations" in Chapter 3, and draw wireframe objects with antialiasing. Try it in either RGBA or color-index mode. Also try different line widths or point sizes to see their effects.

Antialiasing Polygons

Antialiasing the edges of filled polygons is similar to antialiasing points and lines. When different polygons have overlapping edges, you need to blend the color values appropriately. You can either use the method described in this section, or you can use the accumulation buffer to perform antialiasing for your entire scene. Using the accumulation buffer, which is described in Chapter 10, is easier from your point of view, but it's much more computation-intensive and therefore slower. However, as you'll see, the method described here is rather cumbersome.

Note: If you draw your polygons as points at the vertices or as outlines - that is, by passing GL_POINT or GL_LINE to glPolygonMode() - point or line antialiasing is applied, if enabled as described earlier. The rest of this section addresses polygon antialiasing when you're using GL_FILL as the polygon mode.

In theory, you can antialias polygons in either RGBA or color-index mode. However, object intersections affect polygon antialiasing more than they affect point or line antialiasing, so rendering order and blending accuracy become more critical. In fact, they're so critical that if you're antialiasing more than one polygon, you need to order the polygons from front to back and then use glBlendFunc() with GL_SRC_ALPHA_SATURATE for the source factor and GL_ONE for the destination factor. Thus, antialiasing polygons in color-index mode normally isn't practical.

To antialias polygons in RGBA mode, you use the alpha value to represent coverage values of polygon edges. You need to enable polygon antialiasing by passing GL_POLYGON_SMOOTH to glEnable(). This causes pixels on the edges of the polygon to be assigned fractional alpha values based on their coverage, as though they were lines being antialiased. Also, if you desire, you can supply a value for GL_POLYGON_SMOOTH_HINT.

Now you need to blend overlapping edges appropriately. First, turn off the depth buffer so that you have control over how overlapping pixels are drawn. Then set the blending factors to GL_SRC_ALPHA_SATURATE (source) and GL_ONE (destination). With this specialized blending function, the final color is the sum of the destination color and the scaled source color; the scale factor is the smaller of either the incoming source alpha value or one minus the destination alpha value. This means that for a pixel with a large alpha value, successive incoming pixels have little effect on the final color because one minus the destination alpha is almost zero. With this method, a pixel on the edge of a polygon might be blended eventually with the colors from another polygon that's drawn later. Finally, you need to sort all the polygons in your scene so that they're ordered from front to back before drawing them.

Example 6-5 shows how to antialias filled polygons; clicking the left mouse button toggles the antialiasing on and off. Note that backward-facing polygons are culled and that the alpha values in the color buffer are cleared to zero before any drawing. Pressing the `t' key toggles the antialiasing on and off.

Note: Your color buffer must store alpha values for this technique to work correctly. Make sure you request GLUT_ALPHA and receive a legitimate window.

Example 6-5 : Antialiasing Filled Polygons: aapoly.c

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

GLboolean polySmooth = GL_TRUE;
static void init(void)
{
   glCullFace (GL_BACK);
   glEnable (GL_CULL_FACE);
   glBlendFunc (GL_SRC_ALPHA_SATURATE, GL_ONE);
   glClearColor (0.0, 0.0, 0.0, 0.0);
}

#define NFACE 6
#define NVERT 8
void drawCube(GLdouble x0, GLdouble x1, GLdouble y0,
              GLdouble y1, GLdouble z0, GLdouble z1)
{
   static GLfloat v[8][3];
   static GLfloat c[8][4] = {
      {0.0, 0.0, 0.0, 1.0}, {1.0, 0.0, 0.0, 1.0},
      {0.0, 1.0, 0.0, 1.0}, {1.0, 1.0, 0.0, 1.0},
      {0.0, 0.0, 1.0, 1.0}, {1.0, 0.0, 1.0, 1.0},
      {0.0, 1.0, 1.0, 1.0}, {1.0, 1.0, 1.0, 1.0}
   };

/*  indices of front, top, left, bottom, right, back faces  */
   static GLubyte indices[NFACE][4] = {
      {4, 5, 6, 7}, {2, 3, 7, 6}, {0, 4, 7, 3},
      {0, 1, 5, 4}, {1, 5, 6, 2}, {0, 3, 2, 1}
   };

   v[0][0] = v[3][0] = v[4][0] = v[7][0] = x0;
   v[1][0] = v[2][0] = v[5][0] = v[6][0] = x1;
   v[0][1] = v[1][1] = v[4][1] = v[5][1] = y0;
   v[2][1] = v[3][1] = v[6][1] = v[7][1] = y1;
   v[0][2] = v[1][2] = v[2][2] = v[3][2] = z0;
   v[4][2] = v[5][2] = v[6][2] = v[7][2] = z1;

#ifdef GL_VERSION_1_1
   glEnableClientState (GL_VERTEX_ARRAY);
   glEnableClientState (GL_COLOR_ARRAY);
   glVertexPointer (3, GL_FLOAT, 0, v);
   glColorPointer (4, GL_FLOAT, 0, c);
   glDrawElements(GL_QUADS, NFACE*4, GL_UNSIGNED_BYTE, indices);
   glDisableClientState (GL_VERTEX_ARRAY);
   glDisableClientState (GL_COLOR_ARRAY);
#else
   printf ("If this is GL Version 1.0, ");
   printf ("vertex arrays are not supported.\n");
   exit(1);
#endif
}
/*  Note:  polygons must be drawn from front to back
 *  for proper blending.
 */
void display(void)
{
   if (polySmooth) {
      glClear (GL_COLOR_BUFFER_BIT);
      glEnable (GL_BLEND);
      glEnable (GL_POLYGON_SMOOTH);
      glDisable (GL_DEPTH_TEST);
   }
   else {
      glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      glDisable (GL_BLEND);
      glDisable (GL_POLYGON_SMOOTH);
      glEnable (GL_DEPTH_TEST);
   }

   glPushMatrix ();
      glTranslatef (0.0, 0.0, -8.0);
      glRotatef (30.0, 1.0, 0.0, 0.0);
      glRotatef (60.0, 0.0, 1.0, 0.0);
      drawCube(-0.5, 0.5, -0.5, 0.5, -0.5, 0.5);
   glPopMatrix ();

   glFlush ();
}

void reshape(int w, int h)
{
   glViewport(0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluPerspective(30.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
}
void keyboard(unsigned char key, int x, int y)
{
   switch (key) {
      case `t':
      case `T':
         polySmooth = !polySmooth;
         glutPostRedisplay();
         break;
      case 27:
         exit(0);  /*  Escape key  */
         break;
      default:
         break;
   }
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB
                        | GLUT_ALPHA | GLUT_DEPTH);
   glutInitWindowSize(200, 200);
   glutCreateWindow(argv[0]);
   init ();
   glutReshapeFunc (reshape);
   glutKeyboardFunc (keyboard);
   glutDisplayFunc (display);
   glutMainLoop();
   return 0;
}

Fog

Computer images sometimes seem unrealistically sharp and well defined. Antialiasing makes an object appear more realistic by smoothing its edges. Additionally, you can make an entire image appear more natural by adding fog, which makes objects fade into the distance. Fog is a general term that describes similar forms of atmospheric effects; it can be used to simulate haze, mist, smoke, or pollution. (See Plate 9.) Fog is essential in visual-simulation applications, where limited visibility needs to be approximated. It's often incorporated into flight-simulator displays.

When fog is enabled, objects that are farther from the viewpoint begin to fade into the fog color. You can control the density of the fog, which determines the rate at which objects fade as the distance increases, as well as the fog's color. Fog is available in both RGBA and color-index modes, although the calculations are slightly different in the two modes. Since fog is applied after matrix transformations, lighting, and texturing are performed, it affects transformed, lit, and textured objects. Note that with large simulation programs, fog can improve performance, since you can choose not to draw objects that would be too fogged to be visible.

All types of geometric primitives can be fogged, including points and lines. Using the fog effect on points and lines is also called depth-cuing (as shown in Plate 2) and is popular in molecular modeling and other applications.

Using Fog

Using fog is easy. You enable it by passing GL_FOG to glEnable(), and you choose the color and the equation that controls the density with glFog*(). If you want, you can supply a value for GL_FOG_HINT with glHint(), as described on Table 6-2. Example 6-6 draws five red spheres, each at a different distance from the viewpoint. Pressing the `f' key selects among the three different fog equations, which are described in the next section.

Example 6-6 : Five Fogged Spheres in RGBA Mode: fog.c

#include <GL/gl.h>
#include <GL/glu.h>
#include <math.h>
#include <GL/glut.h>
#include <stdlib.h>
#include <stdio.h>

static GLint fogMode;

static void init(void)
{
   GLfloat position[] = { 0.5, 0.5, 3.0, 0.0 };

   glEnable(GL_DEPTH_TEST);

   glLightfv(GL_LIGHT0, GL_POSITION, position);
   glEnable(GL_LIGHTING);
   glEnable(GL_LIGHT0);
   {
      GLfloat mat[3] = {0.1745, 0.01175, 0.01175};
      glMaterialfv (GL_FRONT, GL_AMBIENT, mat);
      mat[0] = 0.61424; mat[1] = 0.04136; mat[2] = 0.04136;
      glMaterialfv (GL_FRONT, GL_DIFFUSE, mat);
      mat[0] = 0.727811; mat[1] = 0.626959; mat[2] = 0.626959;
      glMaterialfv (GL_FRONT, GL_SPECULAR, mat);
      glMaterialf (GL_FRONT, GL_SHININESS, 0.6*128.0);
   }

   glEnable(GL_FOG);
   {
      GLfloat fogColor[4] = {0.5, 0.5, 0.5, 1.0};

      fogMode = GL_EXP;
      glFogi (GL_FOG_MODE, fogMode);
      glFogfv (GL_FOG_COLOR, fogColor);
      glFogf (GL_FOG_DENSITY, 0.35);
      glHint (GL_FOG_HINT, GL_DONT_CARE);
      glFogf (GL_FOG_START, 1.0);
      glFogf (GL_FOG_END, 5.0);
   }
   glClearColor(0.5, 0.5, 0.5, 1.0);  /* fog color */
}

static void renderSphere (GLfloat x, GLfloat y, GLfloat z)
{
   glPushMatrix();
   glTranslatef (x, y, z);
   glutSolidSphere(0.4, 16, 16);
   glPopMatrix();
}

/* display() draws 5 spheres at different z positions.
 */
void display(void)
{
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   renderSphere (-2., -0.5, -1.0);
   renderSphere (-1., -0.5, -2.0);
   renderSphere (0., -0.5, -3.0);
   renderSphere (1., -0.5, -4.0);
   renderSphere (2., -0.5, -5.0);
   glFlush();
}

void reshape(int w, int h)
{
   glViewport(0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   if (w <= h)
      glOrtho (-2.5, 2.5, -2.5*(GLfloat)h/(GLfloat)w,
         2.5*(GLfloat)h/(GLfloat)w, -10.0, 10.0);
   else
      glOrtho (-2.5*(GLfloat)w/(GLfloat)h,
         2.5*(GLfloat)w/(GLfloat)h, -2.5, 2.5, -10.0, 10.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity ();
}

void keyboard(unsigned char key, int x, int y)
{
   switch (key) {
      case `f':
      case `F':
         if (fogMode == GL_EXP) {
            fogMode = GL_EXP2;
            printf ("Fog mode is GL_EXP2\n");
         }
         else if (fogMode == GL_EXP2) {
            fogMode = GL_LINEAR;
            printf ("Fog mode is GL_LINEAR\n");
         }
         else if (fogMode == GL_LINEAR) {
            fogMode = GL_EXP;
            printf ("Fog mode is GL_EXP\n");
         }
         glFogi (GL_FOG_MODE, fogMode);
         glutPostRedisplay();
         break;
      case 27:
         exit(0);
         break;
      default:
         break;
   }
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);
   glutInitWindowSize(500, 500);
   glutCreateWindow(argv[0]);
   init();
   glutReshapeFunc (reshape);
   glutKeyboardFunc (keyboard);
   glutDisplayFunc (display);
   glutMainLoop();
   return 0;
}

Fog Equations

Fog blends a fog color with an incoming fragment's color using a fog blending factor. This factor, f, is computed with one of these three equations and then clamped to the range [0,1].

In these three equations, z is the eye-coordinate distance between the viewpoint and the fragment center. The values for density, start, and end are all specified with glFog*(). The f factor is used differently, depending on whether you're in RGBA mode or color-index mode, as explained in the next subsections.

void glFog{if}(GLenum pname, TYPE param);
void glFog{if}v(GLenum pname, TYPE *params);
Sets the parameters and function for calculating fog. If pname is GL_FOG_MODE, then param is either GL_EXP (the default), GL_EXP2, or GL_LINEAR to select one of the three fog factors. If pname is GL_FOG_DENSITY, GL_FOG_START, or GL_FOG_END, then param is (or points to, with the vector version of the command) a value for density, start, or end in the equations. (The default values are 1, 0, and 1, respectively.) In RGBA mode, pname can be GL_FOG_COLOR, in which case params points to four values that specify the fog's RGBA color values. The corresponding value for pname in color-index mode is GL_FOG_INDEX, for which param is a single value specifying the fog's color index.

Figure 6-4 plots the fog-density equations for various values of the parameters.

Figure 6-4 : Fog-Density Equations

Fog in RGBA Mode

In RGBA mode, the fog factor f is used as follows to calculate the final fogged color:

C = f Ci + (1 - f ) Cf

where Ci represents the incoming fragment's RGBA values and Cf the fog-color values assigned with GL_FOG_COLOR.

Fog in Color-Index Mode

In color-index mode, the final fogged color index is computed as follows:

I = Ii + (1 - f ) If

where Ii is the incoming fragment's color index and If is the fog's color index as specified with GL_FOG_INDEX.

To use fog in color-index mode, you have to load appropriate values in a color ramp. The first color in the ramp is the color of the object without fog, and the last color in the ramp is the color of the completely fogged object. You probably want to use glClearIndex() to initialize the background color index so that it corresponds to the last color in the ramp; this way, totally fogged objects blend into the background. Similarly, before objects are drawn, you should call glIndex*() and pass in the index of the first color in the ramp (the unfogged color). Finally, to apply fog to different colored objects in the scene, you need to create several color ramps and call glIndex*() before each object is drawn to set the current color index to the start of each color ramp. Example 6-7 illustrates how to initialize appropriate conditions and then apply fog in color-index mode.

Example 6-7 : Fog in Color-Index Mode: fogindex.c

#include <GL/gl.h>
#include <GL/glu.h>
#include <math.h>
#include <GL/glut.h>
#include <stdlib.h>
#include <stdio.h>

/*  Initialize color map and fog.  Set screen clear color
 *  to end of color ramp.
 */
#define NUMCOLORS 32
#define RAMPSTART 16

static void init(void)
{
   int i;

   glEnable(GL_DEPTH_TEST);

   for (i = 0; i < NUMCOLORS; i++) {
      GLfloat shade;
      shade = (GLfloat) (NUMCOLORS-i)/(GLfloat) NUMCOLORS;
      glutSetColor (RAMPSTART + i, shade, shade, shade);
   }
   glEnable(GL_FOG);

   glFogi (GL_FOG_MODE, GL_LINEAR);
   glFogi (GL_FOG_INDEX, NUMCOLORS);
   glFogf (GL_FOG_START, 1.0);
   glFogf (GL_FOG_END, 6.0);
   glHint (GL_FOG_HINT, GL_NICEST);
   glClearIndex((GLfloat) (NUMCOLORS+RAMPSTART-1));
}

static void renderSphere (GLfloat x, GLfloat y, GLfloat z)
{
   glPushMatrix();
   glTranslatef (x, y, z);
   glutWireSphere(0.4, 16, 16);
   glPopMatrix();
}

/*  display() draws 5 spheres at different z positions.
 */
void display(void)
{
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   glIndexi (RAMPSTART);

   renderSphere (-2., -0.5, -1.0);
   renderSphere (-1., -0.5, -2.0);
   renderSphere (0., -0.5, -3.0);
   renderSphere (1., -0.5, -4.0);
   renderSphere (2., -0.5, -5.0);

   glFlush();
}

void reshape(int w, int h)
{
   glViewport(0, 0, w, h);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   if (w <= h)
      glOrtho (-2.5, 2.5, -2.5*(GLfloat)h/(GLfloat)w,
         2.5*(GLfloat)h/(GLfloat)w, -10.0, 10.0);
   else
      glOrtho (-2.5*(GLfloat)w/(GLfloat)h,
         2.5*(GLfloat)w/(GLfloat)h, -2.5, 2.5, -10.0, 10.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity ();
}

void keyboard(unsigned char key, int x, int y)
{

   switch (key) {
      case 27:
         exit(0);
   }
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_SINGLE | GLUT_INDEX | GLUT_DEPTH);
   glutInitWindowSize(500, 500);
   glutCreateWindow(argv[0]);
   init();
   glutReshapeFunc (reshape);
   glutKeyboardFunc (keyboard);
   glutDisplayFunc (display);
   glutMainLoop();
   return 0;
}

Polygon Offset

If you want to highlight the edges of a solid object, you might try to draw the object with polygon mode GL_FILL and then draw it again, but in a different color with polygon mode GL_LINE. However, because lines and filled polygons are not rasterized in exactly the same way, the depth values generated for pixels on a line are usually not the same as the depth values for a polygon edge, even between the same two vertices. The highlighting lines may fade in and out of the coincident polygons, which is sometimes called "stitching" and is visually unpleasant.

The visual unpleasantness can be eliminated by using polygon offset, which adds an appropriate offset to force coincident z values apart to cleanly separate a polygon edge from its highlighting line. (The stencil buffer, described in "Stencil Test" in Chapter 10, can also be used to eliminate stitching. However, polygon offset is almost always faster than stenciling.) Polygon offset is also useful for applying decals to surfaces, rendering images with hidden-line removal. In addition to lines and filled polygons, this technique can also be used with points.

There are three different ways to turn on polygon offset, one for each type of polygon rasterization mode: GL_FILL, GL_LINE, or GL_POINT. You enable the polygon offset by passing the appropriate parameter to glEnable(), either GL_POLYGON_OFFSET_FILL, GL_POLYGON_OFFSET_LINE, or GL_POLYGON_OFFSET_POINT. You must also call glPolygonMode() to set the current polygon rasterization method.

void glPolygonOffset(GLfloat factor, GLfloat units);
When enabled, the depth value of each fragment is added to a calculated offset value. The offset is added before the depth test is performed and before the depth value is written into the depth buffer. The offset value o is calculated by:
o = m * factor + r * units
where m is the maximum depth slope of the polygon and r is the smallest value guaranteed to produce a resolvable difference in window coordinate depth values. The value r is an implementation-specific constant.

To achieve a nice rendering of the highlighted solid object without visual artifacts, you can either add a positive offset to the solid object (push it away from you) or a negative offset to the wireframe (pull it towards you). The big question is: "How much offset is enough?" Unfortunately, the offset required depends upon various factors, including the depth slope of each polygon and the width of the lines in the wireframe.

OpenGL calculates the depth slope (see Figure 6-5) of a polygon for you, but it's important that you understand what the depth slope is, so that you choose a reasonable value for factor. The depth slope is the change in z (depth) values divided by the change in either x or y coordinates, as you traverse a polygon. The depth values are in window coordinates, clamped to the range [0, 1]. To estimate the maximum depth slope of a polygon (m in the offset equation), use this formula:

Figure 6-5 : Polygons and Their Depth Slopes

For polygons that are parallel to the near and far clipping planes, the depth slope is zero. For the polygons in your scene with a depth slope near zero, only a small, constant offset is needed. To create a small, constant offset, you can pass factor=0.0 and units=1.0 to glPolygonOffset().

For polygons that are at a great angle to the clipping planes, the depth slope can be significantly greater than zero, and a larger offset may be needed. Small, non-zero values for factor, such as 0.75 or 1.0, are probably enough to generate distinct depth values and eliminate the unpleasant visual artifacts.

Example 6-8 shows a portion of code, where a display list (which presumably draws a solid object) is first rendered with lighting, the default GL_FILL polygon mode, and polygon offset with factor of 1.0 and units of 1.0. These values ensure that the offset is enough for all polygons in your scene, regardless of depth slope. (These values may actually be a little more offset than the minimum needed, but too much offset is less noticeable than too little.) Then, to highlight the edges of the first object, the object is rendered as an unlit wireframe with the offset disabled.

Example 6-8 : Polygon Offset to Eliminate Visual Artifacts: polyoff.c

   glEnable(GL_LIGHTING);
   glEnable(GL_LIGHT0);
   glEnable(GL_POLYGON_OFFSET_FILL);
   glPolygonOffset(1.0, 1.0);
   glCallList (list);
   glDisable(GL_POLYGON_OFFSET_FILL);

   glDisable(GL_LIGHTING);
   glDisable(GL_LIGHT0);
   glColor3f (1.0, 1.0, 1.0);
   glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
   glCallList (list);
   glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

In a few situations, the simplest values for factor and units (1.0 and 1.0) aren't the answers. For instance, if the width of the lines that are highlighting the edges are greater than one, then increasing the value of factor may be necessary. Also, since depth values are unevenly transformed into window coordinates when using perspective projection (see "The Transformed Depth Coordinate" in Chapter 3), less offset is needed for polygons that are closer to the near clipping plane, and more offset is needed for polygons that are further away. Once again, experimenting with the value of factor may be warranted.


Chapter 7 - OpenGL Programming Guide

OpenGL Programming Guide
Programming Guide > Chapter 7

Chapter 7
Display Lists

Chapter Objectives

After reading this chapter, you'll be able to do the following:

  • Understand how display lists can be used along with commands in immediate mode to organize your data and improve performance
  • Maximize performance by knowing how and when to use display lists

A display list is a group of OpenGL commands that have been stored for later execution. When a display list is invoked, the commands in it are executed in the order in which they were issued. Most OpenGL commands can be either stored in a display list or issued in immediate mode, which causes them to be executed immediately. You can freely mix immediate-mode programming and display lists within a single program. The programming examples you've seen so far have used immediate mode. This chapter discusses what display lists are and how best to use them. It has the following major sections:

  • "Why Use Display Lists?" explains when to use display lists.
  • "An Example of Using a Display List" gives a brief example, showing the basic commands for using display lists.
  • "Display-List Design Philosophy" explains why certain design choices were made (such as making display lists uneditable) and what performance optimizations you might expect to see when using display lists.
  • "Creating and Executing a Display List" discusses in detail the commands for creating, executing, and deleting display lists.
  • "Executing Multiple Display Lists" shows how to execute several display lists in succession, using a small character set as an example.
  • "Managing State Variables with Display Lists" illustrates how to use display lists to save and restore OpenGL commands that set state variables.

Why Use Display Lists?

Display lists may improve performance since you can use them to store OpenGL commands for later execution. It is often a good idea to cache commands in a display list if you plan to redraw the same geometry multiple times, or if you have a set of state changes that need to be applied multiple times. Using display lists, you can define the geometry and/or state changes once and execute them multiple times.

To see how you can use display lists to store geometry just once, consider drawing a tricycle. The two wheels on the back are the same size but are offset from each other. The front wheel is larger than the back wheels and also in a different location. An efficient way to render the wheels on the tricycle would be to store the geometry for one wheel in a display list then execute the list three times. You would need to set the modelview matrix appropriately each time before executing the list to calculate the correct size and location for the wheels.

When running OpenGL programs remotely to another machine on the network, it is especially important to cache commands in a display list. In this case, the server is a different machine than the host. (See "What Is OpenGL?" in Chapter 1 for a discussion of the OpenGL client-server model.) Since display lists are part of the server state and therefore reside on the server machine, you can reduce the cost of repeatedly transmitting that data over a network if you store repeatedly used commands in a display list.

When running locally, you can often improve performance by storing frequently used commands in a display list. Some graphics hardware may store display lists in dedicated memory or may store the data in an optimized form that is more compatible with the graphics hardware or software. (See "Display-List Design Philosophy" for a detailed discussion of these optimizations.)

An Example of Using a Display List

A display list is a convenient and efficient way to name and organize a set of OpenGL commands. For example, suppose you want to draw a torus and view it from different angles. The most efficient way to do this would be to store the torus in a display list. Then whenever you want to change the view, you would change the modelview matrix and execute the display list to draw the torus. Example 7-1 illustrates this.

Example 7-1 : Creating a Display List: torus.c

#include <GL/gl.h>
#include <GL/glu.h>
#include <stdio.h>
#include <math.h>
#include <GL/glut.h>
#include <stdlib.h>

GLuint theTorus;

/* Draw a torus */
static void torus(int numc, int numt)
{
   int i, j, k;
   double s, t, x, y, z, twopi;

   twopi = 2 * (double)M_PI;
   for (i = 0; i < numc; i++) {
      glBegin(GL_QUAD_STRIP);
      for (j = 0; j <= numt; j++) {
         for (k = 1; k >= 0; k--) {
            s = (i + k) % numc + 0.5;
            t = j % numt;

            x = (1+.1*cos(s*twopi/numc))*cos(t*twopi/numt);
            y = (1+.1*cos(s*twopi/numc))*sin(t*twopi/numt);
            z = .1 * sin(s * twopi / numc);
            glVertex3f(x, y, z);
         }
      }
      glEnd();
   }
}

/* Create display list with Torus and initialize state*/
static void init(void)
{
   theTorus = glGenLists (1);
   glNewList(theTorus, GL_COMPILE);
   torus(8, 25);
   glEndList();

   glShadeModel(GL_FLAT);
   glClearColor(0.0, 0.0, 0.0, 0.0);
}

void display(void)
{
   glClear(GL_COLOR_BUFFER_BIT);
   glColor3f (1.0, 1.0, 1.0);
   glCallList(theTorus);
   glFlush();
}

void reshape(int w, int h)
{
   glViewport(0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluPerspective(30, (GLfloat) w/(GLfloat) h, 1.0, 100.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
   gluLookAt(0, 0, 10, 0, 0, 0, 0, 1, 0);
}

/* Rotate about x-axis when "x" typed; rotate about y-axis
   when "y" typed; "i" returns torus to original view */
void keyboard(unsigned char key, int x, int y)
{
   switch (key) {
   case `x':
   case `X':
      glRotatef(30.,1.0,0.0,0.0);
      glutPostRedisplay();
      break;
   case `y':
   case `Y':
      glRotatef(30.,0.0,1.0,0.0);
      glutPostRedisplay();
      break;
   case `i':
   case `I':
      glLoadIdentity();
      gluLookAt(0, 0, 10, 0, 0, 0, 0, 1, 0);
      glutPostRedisplay();
      break;
   case 27:
      exit(0);
      break;
   }
}

int main(int argc, char **argv)
{
   glutInitWindowSize(200, 200);
   glutInit(&argc, argv);
   glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
   glutCreateWindow(argv[0]);
   init();
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyboard);
   glutDisplayFunc(display);
   glutMainLoop();
   return 0;
}

Let's start by looking at init(). It creates a display list for the torus and initializes the viewing matrices and other rendering state. Note that the routine for drawing a torus (torus()) is bracketed by glNewList() and glEndList(), which defines a display list. The argument listName for glNewList() is an integer index, generated by glGenLists(), that uniquely identifies this display list.

The user can rotate the torus about the x- or y-axis by pressing the `x' or `y' key when the window has focus. Whenever this happens, the callback function keyboard() is called, which concatenates a 30-degree rotation matrix (about the x- or y-axis) with the current modelview matrix. Then glutPostRedisplay() is called, which will cause glutMainLoop() to call display() and render the torus after other events have been processed. When the `i' key is pressed, keyboard() restores the initial modelview matrix and returns the torus to its original location.

The display() function is very simple: It clears the window and then calls glCallList() to execute the commands in the display list. If we hadn't used display lists, display() would have to reissue the commands to draw the torus each time it was called.

A display list contains only OpenGL commands. In Example 7-1, only the glBegin(), glVertex(), and glEnd() calls are stored in the display list. The parameters for the calls are evaluated, and their values are copied into the display list when it is created. All the trigonometry to create the torus is done only once, which should increase rendering performance. However, the values in the display list can't be changed later. And once a command has been stored in a list it is not possible to remove it. Neither can you add any new commands to the list after it has been defined. You can delete the entire display list and create a new one, but you can't edit it.

Note: Display lists also work well with GLU commands, since those operations are ultimately broken down into low-level OpenGL commands, which can easily be stored in display lists. Use of display lists with GLU is particularly important for optimizing performance of GLU tessellators and NURBS.

Display-List Design Philosophy

To optimize performance, an OpenGL display list is a cache of commands rather than a dynamic database. In other words, once a display list is created, it can't be modified. If a display list were modifiable, performance could be reduced by the overhead required to search through the display list and perform memory management. As portions of a modifiable display list were changed, memory allocation and deallocation might lead to memory fragmentation. Any modifications that the OpenGL implementation made to the display-list commands in order to make them more efficient to render would need to be redone. Also, the display list may be difficult to access, cached somewhere over a network or a system bus.

The way in which the commands in a display list are optimized may vary from implementation to implementation. For example, a command as simple as glRotate*() might show a significant improvement if it's in a display list, since the calculations to produce the rotation matrix aren't trivial (they can involve square roots and trigonometric functions). In the display list, however, only the final rotation matrix needs to be stored, so a display-list rotation command can be executed as fast as the hardware can execute glMultMatrix*(). A sophisticated OpenGL implementation might even concatenate adjacent transformation commands into a single matrix multiplication.

Although you're not guaranteed that your OpenGL implementation optimizes display lists for any particular uses, the execution of display lists isn't slower than executing the commands contained within them individually. There is some overhead, however, involved in jumping to a display list. If a particular list is small, this overhead could exceed any execution advantage. The most likely possibilities for optimization are listed next, with references to the chapters where the topics are discussed.

  • Matrix operations (Chapter 3). Most matrix operations require OpenGL to compute inverses. Both the computed matrix and its inverse might be stored by a particular OpenGL implementation in a display list.
  • Raster bitmaps and images (Chapter 8). The format in which you specify raster data isn't likely to be one that's ideal for the hardware. When a display list is compiled, OpenGL might transform the data into the representation preferred by the hardware. This can have a significant effect on the speed of raster character drawing, since character strings usually consist of a series of small bitmaps.
  • Lights, material properties, and lighting models (Chapter 5). When you draw a scene with complex lighting conditions, you might change the materials for each item in the scene. Setting the materials can be slow, since it might involve significant calculations. If you put the material definitions in display lists, these calculations don't have to be done each time you switch materials, since only the results of the calculations need to be stored; as a result, rendering lit scenes might be faster. (See "Encapsulating Mode Changes" for more details on using display lists to change such values as lighting conditions.)
  • Textures (Chapter 9). You might be able to maximize efficiency when defining textures by compiling them into a display list, since the display list may allow the texture image to be cached in dedicated texture memory. Then the texture image would not have to be recopied each time it was needed. Also, the hardware texture format might differ from the OpenGL format, and the conversion can be done at display-list compile time rather than during display.

In OpenGL version 1.0, the display list is the primary method to manage textures. However, if the OpenGL implementation that you are using is version 1.1 or greater, then you should store the texture in a texture object instead. (Some version 1.0 implementations have a vendor-specific extension to support texture objects. If your implementation supports texture objects, you are encouraged to use them.)
  • Polygon stipple patterns (Chapter 2).

Some of the commands to specify the properties listed here are context-sensitive, so you need to take this into account to ensure optimum performance. For example, when GL_COLOR_MATERIAL is enabled, some of the material properties will track the current color. (See Chapter 5.) Any glMaterial*() calls that set the same material properties are ignored.

It may improve performance to store state settings with geometry. For example, suppose you want to apply a transformation to some geometric objects and then draw the result. Your code may look like this:

glNewList(1, GL_COMPILE);
draw_some_geometric_objects();
glEndList();

glLoadMatrix(M);
glCallList(1);

However, if the geometric objects are to be transformed in the same way each time, it is better to store the matrix in the display list. For example, if you were to write your code as follows, some implementations may be able to improve performance by transforming the objects when they are defined instead of each time they are drawn:

glNewList(1, GL_COMPILE);
glLoadMatrix(M);
draw_some_geometric_objects();
glEndList();

glCallList(1);

A more likely situation occurs when rendering images. As you will see in Chapter 8, you can modify pixel transfer state variables and control the way images and bitmaps are rasterized. If the commands that set these state variables precede the definition of the image or bitmap in the display list, the implementation may be able to perform some of the operations ahead of time and cache the result.

Remember that display lists have some disadvantages. Very small lists may not perform well since there is some overhead when executing a list. Another disadvantage is the immutability of the contents of a display list. To optimize performance, an OpenGL display list can't be changed and its contents can't be read. If the application needs to maintain data separately from the display list (for example, for continued data processing), then a lot of additional memory may be required.

Creating and Executing a Display List

As you've already seen, glNewList() and glEndList() are used to begin and end the definition of a display list, which is then invoked by supplying its identifying index with glCallList(). In Example 7-2, a display list is created in the init() routine. This display list contains OpenGL commands to draw a red triangle. Then in the display() routine, the display list is executed ten times. In addition, a line is drawn in immediate mode. Note that the display list allocates memory to store the commands and the values of any necessary variables.

Example 7-2 : Using a Display List: list.c

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <stdlib.h>

GLuint listName;

static void init (void)
{
   listName = glGenLists (1);
   glNewList (listName, GL_COMPILE);
      glColor3f (1.0, 0.0, 0.0);  /*  current color red  */
      glBegin (GL_TRIANGLES);
      glVertex2f (0.0, 0.0);
      glVertex2f (1.0, 0.0);
      glVertex2f (0.0, 1.0);
      glEnd ();
      glTranslatef (1.5, 0.0, 0.0); /*  move position  */
   glEndList ();
   glShadeModel (GL_FLAT);
}

static void drawLine (void)
{
   glBegin (GL_LINES);
   glVertex2f (0.0, 0.5);
   glVertex2f (15.0, 0.5);
   glEnd ();
}

void display(void)
{
   GLuint i;

   glClear (GL_COLOR_BUFFER_BIT);
   glColor3f (0.0, 1.0, 0.0);  /*  current color green  */
   for (i = 0; i < 10; i++)    /*  draw 10 triangles    */
      glCallList (listName);
   drawLine ();  /*  is this line green?  NO!  */
                 /*  where is the line drawn?  */
   glFlush ();
}

void reshape(int w, int h)
{
   glViewport(0, 0, w, h);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   if (w <= h)
      gluOrtho2D (0.0, 2.0, -0.5 * (GLfloat) h/(GLfloat) w,
         1.5 * (GLfloat) h/(GLfloat) w);
   else
      gluOrtho2D (0.0, 2.0*(GLfloat) w/(GLfloat) h, -0.5, 1.5);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
}

void keyboard(unsigned char key, int x, int y)
{
   switch (key) {
      case 27:
         exit(0);
   }
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
   glutInitWindowSize(650, 50);
   glutCreateWindow(argv[0]);
   init ();
   glutReshapeFunc (reshape);
   glutKeyboardFunc (keyboard);
   glutDisplayFunc (display);
   glutMainLoop();
   return 0;
}

The glTranslatef() routine in the display list alters the position of the next object to be drawn. Without it, calling the display list twice would just draw the triangle on top of itself. The drawLine() routine, which is called in immediate mode, is also affected by the ten glTranslatef() calls that precede it. So if you call transformation commands within a display list, don't forget to take into account the effect those commands will have later in your program.

Only one display list can be created at a time. In other words, you must eventually follow glNewList() with glEndList() to end the creation of a display list before starting another one. As you might expect, calling glEndList() without having started a display list generates the error GL_INVALID_OPERATION. (See "Error Handling" in Chapter 14 for more information about processing errors.)

Naming and Creating a Display List

Each display list is identified by an integer index. When creating a display list, you want to be careful that you don't accidentally choose an index that's already in use, thereby overwriting an existing display list. To avoid accidental deletions, use glGenLists() to generate one or more unused indices.

GLuint glGenLists(GLsizei range);
Allocates range number of contiguous, previously unallocated display-list indices. The integer returned is the index that marks the beginning of a contiguous block of empty display-list indices. The returned indices are all marked as empty and used, so subsequent calls to glGenLists() don't return these indices until they're deleted. Zero is returned if the requested number of indices isn't available, or if range is zero.

In the following example, a single index is requested, and if it proves to be available, it's used to create a new display list:

listIndex = glGenLists(1);
if (listIndex != 0) {
   glNewList(listIndex,GL_COMPILE);
      ...
   glEndList();
}

Note: Zero is not a valid display-list index.

void glNewList (GLuint list, GLenum mode);
Specifies the start of a display list. OpenGL routines that are called subsequently (until glEndList() is called to end the display list) are stored in a display list, except for a few restricted OpenGL routines that can't be stored. (Those restricted routines are executed immediately, during the creation of the display list.) list is a nonzero positive integer that uniquely identifies the display list. The possible values for mode are GL_COMPILE and GL_COMPILE_AND_EXECUTE. Use GL_COMPILE if you don't want the OpenGL commands executed as they're placed in the display list; to cause the commands to be executed immediately as well as placed in the display list for later use, specify GL_COMPILE_AND_EXECUTE.
void glEndList (void);
Marks the end of a display list.

When a display list is created it is stored with the current OpenGL context. Thus, when the context is destroyed, the display list is also destroyed. Some windowing systems allow multiple contexts to share display lists. In this case, the display list is destroyed when the last context in the share group is destroyed.

What's Stored in a Display List

When you're building a display list, only the values for expressions are stored in the list. If values in an array are subsequently changed, the display-list values don't change. In the following code fragment, the display list contains a command to set the current RGBA color to black (0.0, 0.0, 0.0). The subsequent change of the value of the color_vector array to red (1.0, 0.0, 0.0) has no effect on the display list because the display list contains the values that were in effect when it was created.

GLfloat color_vector[3] = {0.0, 0.0, 0.0};
glNewList(1, GL_COMPILE);
   glColor3fv(color_vector);
glEndList();
color_vector[0] = 1.0;

Not all OpenGL commands can be stored and executed from within a display list. For example, commands that set client state and commands that retrieve state values aren't stored in a display list. (Many of these commands are easily identifiable because they return values in parameters passed by reference or return a value directly.) If these commands are called when making a display list, they're executed immediately.

Here are the OpenGL commands that aren't stored in a display list (also, note that glNewList() generates an error if it's called while you're creating a display list). Some of these commands haven't been described yet; you can look in the index to see where they're discussed.

glColorPointer() | glFlush() | glNormalPointer()
glDeleteLists() | glGenLists() | glPixelStore()
glDisableClientState() | glGet*() | glReadPixels()
glEdgeFlagPointer() | glIndexPointer() | glRenderMode()
glEnableClientState() | glInterleavedArrays() | glSelectBuffer()
glFeedbackBuffer() | glIsEnabled() | glTexCoordPointer()
glFinish() | glIsList() | glVertexPointer()

To understand more clearly why these commands can't be stored in a display list, remember that when you're using OpenGL across a network, the client may be on one machine and the server on another. After a display list is created, it resides with the server, so the server can't rely on the client for any information related to the display list. If querying commands, such as glGet*() or glIs*(), were allowed in a display list, the calling program would be surprised at random times by data returned over the network. Without parsing the display list as it was sent, the calling program wouldn't know where to put the data. Thus, any command that returns a value can't be stored in a display list. In addition, commands that change client state, such as glPixelStore(), glSelectBuffer(), and the commands to define vertex arrays, can't be stored in a display list.

The operation of some OpenGL commands depends upon client state. For example, the vertex array specification routines (such as glVertexPointer()glColorPointer(), and glInterleavedArrays()) set client state pointers and cannot be stored in a display list. glArrayElement(), glDrawArrays(), and glDrawElements() send data to the server state to construct primitives from elements in the enabled arrays, so these operations can be stored in a display list. (See "Vertex Arrays" in Chapter 2.) The vertex array data stored in this display list is obtained by dereferencing data from the pointers, not by storing the pointers themselves. Therefore, subsequent changes to the data in the vertex arrays will not affect the definition of the primitive in the display list.

In addition, any commands that use the pixel storage modes use the modes that are in effect when they are placed in the display list. (See "Controlling Pixel-Storage Modes" in Chapter 8.) Other routines that rely upon client state - such as glFlush() and glFinish() - can't be stored in a display list because they depend upon the client state that is in effect when they are executed.

Executing a Display List

After you've created a display list, you can execute it by calling glCallList(). Naturally, you can execute the same display list many times, and you can mix calls to execute display lists with calls to perform immediate-mode graphics, as you've already seen.

void glCallList (GLuint list);
This routine executes the display list specified by list. The commands in the display list are executed in the order they were saved, just as if they were issued without using a display list. If list hasn't been defined, nothing happens.

You can call glCallList() from anywhere within a program, as long as an OpenGL context that can access the display list is active (that is, the context that was active when the display list was created or a context in the same share group). A display list can be created in one routine and executed in a different one, since its index uniquely identifies it. Also, there is no facility to save the contents of a display list into a data file, nor a facility to create a display list from a file. In this sense, a display list is designed for temporary use.

Hierarchical Display Lists

You can create a hierarchical display list, which is a display list that executes another display list by calling glCallList() between a glNewList() and glEndList() pair. A hierarchical display list is useful for an object made of components, especially if some of those components are used more than once. For example, this is a display list that renders a bicycle by calling other display lists to render parts of the bicycle:

glNewList(listIndex,GL_COMPILE);
   glCallList(handlebars);
   glCallList(frame);
   glTranslatef(1.0,0.0,0.0);
   glCallList(wheel);
   glTranslatef(3.0,0.0,0.0);
   glCallList(wheel);
glEndList();

To avoid infinite recursion, there's a limit on the nesting level of display lists; the limit is at least 64, but it might be higher, depending on the implementation. To determine the nesting limit for your implementation of OpenGL, call

glGetIntegerv(GL_MAX_LIST_NESTING, GLint *data);

OpenGL allows you to create a display list that calls another list that hasn't been created yet. Nothing happens when the first list calls the second, undefined one.

You can use a hierarchical display list to approximate an editable display list by wrapping a list around several lower-level lists. For example, to put a polygon in a display list while allowing yourself to be able to easily edit its vertices, you could use the code in Example 7-3.

Example 7-3 : Hierarchical Display List

glNewList(1,GL_COMPILE);
   glVertex3f(v1);
glEndList();
glNewList(2,GL_COMPILE);
   glVertex3f(v2);
glEndList();
glNewList(3,GL_COMPILE);
   glVertex3f(v3);
glEndList();

glNewList(4,GL_COMPILE);
   glBegin(GL_POLYGON);
      glCallList(1);
      glCallList(2);
      glCallList(3);
   glEnd();
glEndList();

To render the polygon, call display list number 4. To edit a vertex, you need only recreate the single display list corresponding to that vertex. Since an index number uniquely identifies a display list, creating one with the same index as an existing one automatically deletes the old one. Keep in mind that this technique doesn't necessarily provide optimal memory usage or peak performance, but it's acceptable and useful in some cases.

Managing Display List Indices

So far, we've recommended the use of glGenLists() to obtain unused display-list indices. If you insist upon avoiding glGenLists(), then be sure to use glIsList() to determine whether a specific index is in use.

GLboolean glIsList(GLuint list);
Returns GL_TRUE if list is already used for a display list and GL_FALSE otherwise.

You can explicitly delete a specific display list or a contiguous range of lists with glDeleteLists(). Using glDeleteLists() makes those indices available again.

void glDeleteLists(GLuint list, GLsizei range);
Deletes range display lists, starting at the index specified by list. An attempt to delete a list that has never been created is ignored.

Executing Multiple Display Lists

OpenGL provides an efficient mechanism to execute several display lists in succession. This mechanism requires that you put the display-list indices in an array and call glCallLists(). An obvious use for such a mechanism occurs when display-list indices correspond to meaningful values. For example, if you're creating a font, each display-list index might correspond to the ASCII value of a character in that font. To have several such fonts, you would need to establish a different initial display-list index for each font. You can specify this initial index by using glListBase() before calling glCallLists().

void glListBase(GLuint base);
Specifies the offset that's added to the display-list indices in glCallLists() to obtain the final display-list indices. The default display-list base is 0. The list base has no effect on glCallList(), which executes only one display list or on glNewList().
void glCallLists(GLsizei n, GLenum type, const GLvoid *lists);
Executes n display lists. The indices of the lists to be executed are computed by adding the offset indicated by the current display-list base (specified with glListBase()) to the signed integer values in the array pointed to by lists.
The type parameter indicates the data type of the values in lists. It can be set to GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT, or GL_FLOAT, indicating that lists should be treated as an array of bytes, unsigned bytes, shorts, unsigned shorts, integers, unsigned integers, or floats, respectively. Type can also be GL_2_BYTES, GL_3_BYTES, or GL_4_BYTES, in which case sequences of 2, 3, or 4 bytes are read from lists and then shifted and added together, byte by byte, to calculate the display-list offset. The following algorithm is used (where byte[0] is the start of a byte sequence).
 /* b = 2, 3, or 4; bytes are numbered 0, 1, 2, 3 in array */

 offset = 0;

 for (i = 0; i < b; i++) {

    offset = offset << 8;

    offset += byte[i];

 }

 index = offset + listbase;
For multiple-byte data, the highest-order data comes first as bytes are taken from the array in order.

As an example of the use of multiple display lists, look at the program fragments in Example 7-4 taken from the full program in Example 7-5. This program draws characters with a stroked font (a set of letters made from line segments). The routine initStrokedFont() sets up the display-list indices for each letter so that they correspond with their ASCII values.

Example 7-4 : Defining Multiple Display Lists

void initStrokedFont(void)
{
   GLuint base;

   base = glGenLists(128);
   glListBase(base);
   glNewList(base+'A', GL_COMPILE);
      drawLetter(Adata); glEndList();
   glNewList(base+'E', GL_COMPILE);
      drawLetter(Edata); glEndList();
   glNewList(base+'P', GL_COMPILE);
      drawLetter(Pdata); glEndList();
   glNewList(base+'R', GL_COMPILE);
      drawLetter(Rdata); glEndList();
   glNewList(base+'S', GL_COMPILE);
      drawLetter(Sdata); glEndList();
   glNewList(base+' ', GL_COMPILE);    /* space character */
      glTranslatef(8.0, 0.0, 0.0);
   glEndList();
}

The glGenLists() command allocates 128 contiguous display-list indices. The first of the contiguous indices becomes the display-list base. A display list is made for each letter; each display-list index is the sum of the base and the ASCII value of that letter. In this example, only a few letters and the space character are created.

After the display lists have been created, glCallLists() can be called to execute the display lists. For example, you can pass a character string to the subroutine printStrokedString():

void printStrokedString(GLbyte *s)
{
   GLint len = strlen(s);
   glCallLists(len, GL_BYTE, s);
}

The ASCII value for each letter in the string is used as the offset into the display-list indices. The current list base is added to the ASCII value of each letter to determine the final display-list index to be executed. The output produced by Example 7-5 is shown in Figure 7-1.

Figure 7-1 : Stroked Font That Defines the Characters A, E, P, R, S

Example 7-5 : Multiple Display Lists to Define a Stroked Font: stroke.c

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <stdlib.h>
#include <string.h>

#define PT 1
#define STROKE 2
#define END 3

typedef struct charpoint {
   GLfloat   x, y;
   int    type;
} CP;

CP Adata[] = {
   { 0, 0, PT}, {0, 9, PT}, {1, 10, PT}, {4, 10, PT},
   {5, 9, PT}, {5, 0, STROKE}, {0, 5, PT}, {5, 5, END}
};

CP Edata[] = {
   {5, 0, PT}, {0, 0, PT}, {0, 10, PT}, {5, 10, STROKE},
   {0, 5, PT}, {4, 5, END}
};

CP Pdata[] = {
   {0, 0, PT}, {0, 10, PT}, {4, 10, PT}, {5, 9, PT}, {5, 6, PT},
   {4, 5, PT}, {0, 5, END}
};

CP Rdata[] = {
   {0, 0, PT}, {0, 10, PT}, {4, 10, PT}, {5, 9, PT}, {5, 6, PT},
   {4, 5, PT}, {0, 5, STROKE}, {3, 5, PT}, {5, 0, END}
};

CP Sdata[] = {
   {0, 1, PT}, {1, 0, PT}, {4, 0, PT}, {5, 1, PT}, {5, 4, PT},
   {4, 5, PT}, {1, 5, PT}, {0, 6, PT}, {0, 9, PT}, {1, 10, PT},
   {4, 10, PT}, {5, 9, END}
};

/*  drawLetter() interprets the instructions from the array
 *  for that letter and renders the letter with line segments.
 */
static void drawLetter(CP *l)
{
   glBegin(GL_LINE_STRIP);
   while (1) {
      switch (l->type) {
         case PT:
            glVertex2fv(&l->x);
            break;
         case STROKE:
            glVertex2fv(&l->x);
            glEnd();
            glBegin(GL_LINE_STRIP);
            break;
         case END:
            glVertex2fv(&l->x);
            glEnd();
            glTranslatef(8.0, 0.0, 0.0);
            return;
      }
      l++;
   }
}

/*  Create a display list for each of 6 characters      */
static void init (void)
{
   GLuint base;

   glShadeModel (GL_FLAT);

   base = glGenLists (128);
   glListBase(base);
   glNewList(base+'A', GL_COMPILE); drawLetter(Adata);
   glEndList();
   glNewList(base+'E', GL_COMPILE); drawLetter(Edata);
   glEndList();
   glNewList(base+'P', GL_COMPILE); drawLetter(Pdata);
   glEndList();
   glNewList(base+'R', GL_COMPILE); drawLetter(Rdata);
   glEndList();
   glNewList(base+'S', GL_COMPILE); drawLetter(Sdata);
   glEndList();
   glNewList(base+' `, GL_COMPILE);
   glTranslatef(8.0, 0.0, 0.0); glEndList();
}

char *test1 = "A SPARE SERAPE APPEARS AS";
char *test2 = "APES PREPARE RARE PEPPERS";

static void printStrokedString(char *s)
{
   GLsizei len = strlen(s);
   glCallLists(len, GL_BYTE, (GLbyte *)s);
}

void display(void)
{
   glClear(GL_COLOR_BUFFER_BIT);
   glColor3f(1.0, 1.0, 1.0);
   glPushMatrix();
   glScalef(2.0, 2.0, 2.0);
   glTranslatef(10.0, 30.0, 0.0);
   printStrokedString(test1);
   glPopMatrix();
   glPushMatrix();
   glScalef(2.0, 2.0, 2.0);
   glTranslatef(10.0, 13.0, 0.0);
   printStrokedString(test2);
   glPopMatrix();
   glFlush();
}

void reshape(int w, int h)
{
   glViewport(0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode (GL_PROJECTION);
   glLoadIdentity ();
   gluOrtho2D (0.0, (GLdouble) w, 0.0, (GLdouble) h);
}

void keyboard(unsigned char key, int x, int y)
{
   switch (key) {
      case ` `:
         glutPostRedisplay();
         break;
      case 27:
         exit(0);
   }
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
   glutInitWindowSize (440, 120);
   glutCreateWindow (argv[0]);
   init ();
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyboard);
   glutDisplayFunc(display);
   glutMainLoop();
   return 0;
}

Managing State Variables with Display Lists

A display list can contain calls that change the value of OpenGL state variables. These values change as the display list is executed, just as if the commands were called in immediate mode and the changes persist after execution of the display list is completed. As previously seen in Example 7-2 and in Example 7-6, which follows, the changes to the current color and current matrix made during the execution of the display list remain in effect after it has been called.

Example 7-6 : Persistence of State Changes after Execution of a Display List

glNewList(listIndex,GL_COMPILE);
   glColor3f(1.0, 0.0, 0.0);
   glBegin(GL_POLYGON);
      glVertex2f(0.0,0.0);
      glVertex2f(1.0,0.0);
      glVertex2f(0.0,1.0);
   glEnd();
   glTranslatef(1.5,0.0,0.0);
glEndList();

So if you now call the following sequence, the line drawn after the display list is drawn with red as the current color and translated by an additional (1.5, 0.0, 0.0):

glCallList(listIndex);
glBegin(GL_LINES);
   glVertex2f(2.0,-1.0);
   glVertex2f(1.0,0.0);
glEnd();

Sometimes you want state changes to persist, but other times you want to save the values of state variables before executing a display list and then restore these values after the list has executed. Remember that you cannot use glGet*() in a display list, so you must use another way to query and store the values of state variables.

You can use glPushAttrib() to save a group of state variables and glPopAttrib() to restore the values when you're ready for them. To save and restore the current matrix, use glPushMatrix() and glPopMatrix() as described in "Manipulating the Matrix Stacks" in Chapter 3. These push and pop routines can be legally cached in a display list. To restore the state variables in Example 7-6, you might use the code shown in Example 7-7.

Example 7-7 : Restoring State Variables within a Display List

glNewList(listIndex,GL_COMPILE);
   glPushMatrix();
   glPushAttrib(GL_CURRENT_BIT);
   glColor3f(1.0, 0.0, 0.0);
   glBegin(GL_POLYGON);
      glVertex2f(0.0,0.0);
      glVertex2f(1.0,0.0);
      glVertex2f(0.0,1.0);
   glEnd();
   glTranslatef(1.5,0.0,0.0);
   glPopAttrib();
   glPopMatrix();
glEndList();

If you use the display list from Example 7-7, which restores values, the code in Example 7-8 draws a green, untranslated line. With the display list in Example 7-6, which doesn't save and restore values, the line is drawn red, and its position is translated ten times (1.5, 0.0, 0.0).

Example 7-8 : The Display List May or May Not Affect drawLine()

void display(void)
{
   GLint i;

   glClear(GL_COLOR_BUFFER_BIT);
   glColor3f(0.0, 1.0, 0.0);  /* set current color to green   */
   for (i = 0; i < 10; i++)
      glCallList(listIndex);  /* display list called 10 times */
   drawLine();        /* how and where does this line appear? */
   glFlush();
}

Encapsulating Mode Changes

You can use display lists to organize and store groups of commands to change various modes or set various parameters. When you want to switch from one group of settings to another, using display lists might be more efficient than making the calls directly, since the settings might be cached in a format that matches the requirements of your graphics system.

Display lists may be more efficient than immediate mode for switching among various lighting, lighting-model, and material-parameter settings. You might also use display lists for stipple patterns, fog parameters, and clipping-plane equations. In general, you'll find that executing display lists is at least as fast as making the relevant calls directly, but remember that some overhead is involved in jumping to a display list.

Example 7-9 shows how to use display lists to switch among three different line stipples. First, you call glGenLists() to allocate a display list for each stipple pattern and create a display list for each pattern. Then, you use glCallList() to switch from one stipple pattern to another.

Example 7-9 : Display Lists for Mode Changes

GLuint offset;
offset = glGenLists(3);

glNewList (offset, GL_COMPILE);
    glDisable (GL_LINE_STIPPLE);
glEndList ();

glNewList (offset+1, GL_COMPILE);
    glEnable (GL_LINE_STIPPLE);
    glLineStipple (1, 0x0F0F);
glEndList ();

glNewList (offset+2, GL_COMPILE);
    glEnable (GL_LINE_STIPPLE);
    glLineStipple (1, 0x1111);
glEndList ();

#define drawOneLine(x1,y1,x2,y2) glBegin(GL_LINES); \
    glVertex2f ((x1),(y1)); glVertex2f ((x2),(y2)); glEnd();

glCallList (offset);
drawOneLine (50.0, 125.0, 350.0, 125.0);

glCallList (offset+1);
drawOneLine (50.0, 100.0, 350.0, 100.0);

glCallList (offset+2);
drawOneLine (50.0, 75.0, 350.0, 75.0);


Chapter 8 - OpenGL Programming Guide

OpenGL Programming Guide
Programming Guide > Chapter 8

Chapter 8
Drawing Pixels, Bitmaps, Fonts, and Images

Chapter Objectives

After reading this chapter, you'll be able to do the following:

  • Position and draw bitmapped data
  • Read pixel data (bitmaps and images) from the framebuffer into processor memory and from memory into the framebuffer
  • Copy pixel data from one color buffer to another, or to another location in the same buffer
  • Magnify or reduce an image as it's written to the framebuffer
  • Control pixel-data formatting and perform other transformations as the data is moved to and from the framebuffer

So far, most of the discussion in this guide has concerned the rendering of geometric data - points, lines, and polygons. Two other important classes of data that can be rendered by OpenGL are

  • Bitmaps, typically used for characters in fonts
  • Image data, which might have been scanned in or calculated

Both bitmaps and image data take the form of rectangular arrays of pixels. One difference between them is that a bitmap consists of a single bit of information about each pixel, and image data typically includes several pieces of data per pixel (the complete red, green, blue, and alpha color components, for example). Also, bitmaps are like masks in that they're used to overlay another image, but image data simply overwrites or is blended with whatever data is in the framebuffer.

This chapter describes how to draw pixel data (bitmaps and images) from processor memory to the framebuffer and how to read pixel data from the framebuffer into processor memory. It also describes how to copy pixel data from one position to another, either from one buffer to another or within a single buffer. This chapter contains the following major sections:

  • "Bitmaps and Fonts" describes the commands for positioning and drawing bitmapped data. Such data may describe a font.
  • "Images" presents the basic information about drawing, reading and copying pixel data.
  • "Imaging Pipeline" describes the operations that are performed on images and bitmaps when they are read from the framebuffer and when they are written to the framebuffer.
  • "Reading and Drawing Pixel Rectangles" covers all the details of how pixel data is stored in memory and how to transform it as it's moved into or out of memory.
  • "Tips for Improving Pixel Drawing Rates" lists tips for getting better performance when drawing pixel rectangles.

In most cases, the necessary pixel operations are simple, so the first three sections might be all you need to read for your application. However, pixel manipulation can be complex - there are many ways to store pixel data in memory, and you can apply any of several transformations to pixels as they're moved to and from the framebuffer. These details are the subject of the fourth section of this chapter. Most likely, you'll want to read this section only when you actually need to make use of the information. The last section provides useful tips to get the best performance when rendering bitmaps and images.

Bitmaps and Fonts

A bitmap is a rectangular array of 0s and 1s that serves as a drawing mask for a corresponding rectangular portion of the window. Suppose you're drawing a bitmap and that the current raster color is red. Wherever there's a 1 in the bitmap, the corresponding pixel is replaced by a red pixel (or combined with a red pixel, depending on which per-fragment operations are in effect. (See "Testing and Operating on Fragments" in Chapter 10.) If there's a 0 in the bitmap, the contents of the pixel are unaffected. The most common use of bitmaps is for drawing characters on the screen.

OpenGL provides only the lowest level of support for drawing strings of characters and manipulating fonts. The commands glRasterPos*() and glBitmap() position and draw a single bitmap on the screen. In addition, through the display-list mechanism, you can use a sequence of character codes to index into a corresponding series of bitmaps representing those characters. (See Chapter 7 for more information about display lists.) You'll have to write your own routines to provide any other support you need for manipulating bitmaps, fonts, and strings of characters.

Consider Example 8-1, which draws the character F three times on the screen. Figure 8-1 shows the F as a bitmap and its corresponding bitmap data.

Figure 8-1 : Bitmapped F and Its Data

Example 8-1 : Drawing a Bitmapped Character: drawf.c

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <stdlib.h>

GLubyte rasters[24] = {
   0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00,
   0xff, 0x00, 0xff, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00,
   0xff, 0xc0, 0xff, 0xc0};

void init(void)
{
   glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
   glClearColor (0.0, 0.0, 0.0, 0.0);
}

void display(void)
{
   glClear(GL_COLOR_BUFFER_BIT);
   glColor3f (1.0, 1.0, 1.0);
   glRasterPos2i (20, 20);
   glBitmap (10, 12, 0.0, 0.0, 11.0, 0.0, rasters);
   glBitmap (10, 12, 0.0, 0.0, 11.0, 0.0, rasters);
   glBitmap (10, 12, 0.0, 0.0, 11.0, 0.0, rasters);
   glFlush();
}

void reshape(int w, int h)
{
   glViewport(0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   glOrtho (0, w, 0, h, -1.0, 1.0);
   glMatrixMode(GL_MODELVIEW);
}

void keyboard(unsigned char key, int x, int y)
{
   switch (key) {
      case 27:
         exit(0);
   }
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
   glutInitWindowSize(100, 100);
   glutInitWindowPosition(100, 100);
   glutCreateWindow(argv[0]);
   init();
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyboard);
   glutDisplayFunc(display);
   glutMainLoop();
   return 0;
}

In Figure 8-1, note that the visible part of the F character is at most 10 bits wide. Bitmap data is always stored in chunks that are multiples of 8 bits, but the width of the actual bitmap doesn't have to be a multiple of 8. The bits making up a bitmap are drawn starting from the lower-left corner: First, the bottom row is drawn, then the next row above it, and so on. As you can tell from the code, the bitmap is stored in memory in this order - the array of rasters begins with 0xc0, 0x00, 0xc0, 0x00 for the bottom two rows of the F and continues to 0xff, 0xc0, 0xff, 0xc0 for the top two rows.

The commands of interest in this example are glRasterPos2i() and glBitmap(); they're discussed in detail in the next section. For now, ignore the call to glPixelStorei(); it describes how the bitmap data is stored in computer memory. (See "Controlling Pixel-Storage Modes" for more information.)

The Current Raster Position

The current raster position is the origin where the next bitmap (or image) is to be drawn. In the F example, the raster position was set by calling glRasterPos*() with coordinates (20, 20), which is where the lower-left corner of the F was drawn:

glRasterPos2i(20, 20);
void glRasterPos{234}{sifd}(TYPE x, TYPE y, TYPE z, TYPE w);
void glRasterPos{234}{sifd}v(TYPE *coords);
Sets the current raster position. The x, y, z, and w arguments specify the coordinates of the raster position. If the vector form of the function is used, the coords array contains the coordinates of the raster position. If glRasterPos2*() is used, z is implicitly set to zero and w is implicitly set to one; similarly, with glRasterPos3*(), w is set to one.

The coordinates of the raster position are transformed to screen coordinates in exactly the same way as coordinates supplied with a glVertex*() command (that is, with the modelview and perspective matrices). After transformation, they either define a valid spot in the viewport, or they're clipped out because the coordinates were outside the viewing volume. If the transformed point is clipped out, the current raster position is invalid.

Note: If you want to specify the raster position in screen coordinates, you'll want to make sure you've specified the modelview and projection matrices for simple 2D rendering, with something like this sequence of commands, where width and height are also the size (in pixels) of the viewport:

glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0.0, (GLfloat) width, 0.0, (GLfloat) height);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();

To obtain the current raster position, you can use the query command glGetFloatv() with GL_CURRENT_RASTER_POSITION as the first argument. The second argument should be a pointer to an array that can hold the (x, y, z, w) values as floating-point numbers. Call glGetBooleanv() with GL_CURRENT_RASTER_POSITION_VALID as the first argument to determine whether the current raster position is valid.

Drawing the Bitmap

Once you've set the desired raster position, you can use the glBitmap() command to draw the data.

void glBitmap(GLsizei width, GLsizei height, GLfloat x bo,
GLfloat y bo, GLfloat x bi,
GLfloat y bi, const GLubyte *bitmap);
Draws the bitmap specified by bitmap, which is a pointer to the bitmap image. The origin of the bitmap is placed at the current raster position. If the current raster position is invalid, nothing is drawn, and the raster position remains invalid. The width and height arguments indicate the width and height, in pixels, of the bitmap. The width need not be a multiple of 8, although the data is stored in unsigned characters of 8 bits each. (In the F example, it wouldn't matter if there were garbage bits in the data beyond the tenth bit; since glBitmap() was called with a width of 10, only 10 bits of the row are rendered.) Use x bo and y bo to define the origin of the bitmap (positive values move the origin up and to the right of the raster position; negative values move it down and to the left); x bi and y bi indicate the x and y increments that are added to the raster position after the bitmap is rasterized (see Figure 8-2).

Figure 8-2 : Bitmap and Its Associated Parameters

Allowing the origin of the bitmap to be placed arbitrarily makes it easy for characters to extend below the origin (typically used for characters with descenders, such as g, j, and y), or to extend beyond the left of the origin (used for various swash characters, which have extended flourishes, or for characters in fonts that lean to the left).

After the bitmap is drawn, the current raster position is advanced by xbi and ybi in the x- and y-directions, respectively. (If you just want to advance the current raster position without drawing anything, call glBitmap() with the bitmap parameter set to NULL and with the width and height set to zero.) For standard Latin fonts, ybi is typically 0.0 and xbi is positive (since successive characters are drawn from left to right). For Hebrew, where characters go from right to left, the xbi values would typically be negative. Fonts that draw successive characters vertically in columns would use zero for xbi and nonzero values for ybi. In Figure 8-2, each time the F is drawn, the current raster position advances by 11 pixels, allowing a 1-pixel space between successive characters.

Since xbo, ybo, xbi, and ybi are floating-point values, characters need not be an integral number of pixels apart. Actual characters are drawn on exact pixel boundaries, but the current raster position is kept in floating point so that each character is drawn as close as possible to where it belongs. For example, if the code in the F example was modified so that xbi is 11.5 instead of 12, and if more characters were drawn, the space between letters would alternate between 1 and 2 pixels, giving the best approximation to the requested 1.5-pixel space.

Note: You can't rotate bitmap fonts because the bitmap is always drawn aligned to the x and y framebuffer axes.

Choosing a Color for the Bitmap

You are familiar with using glColor*() and glIndex*() to set the current color or index to draw geometric primitives. The same commands are used to set different state variables, GL_CURRENT_RASTER_COLOR and GL_CURRENT_RASTER_INDEX, for rendering bitmaps. The raster color state variables are set when glRasterPos*() is called, which can lead to a trap. In the following sequence of code, what is the color of the bitmap?

glColor3f(1.0, 1.0, 1.0);  /* white */
glRasterPos3fv(position);
glColor3f(1.0, 0.0, 0.0);  /* red  */
glBitmap(....);

The bitmap is white! The GL_CURRENT_RASTER_COLOR is set to white when glRasterPos3fv() is called. The second call to glColor3f() changes the value of GL_CURRENT_COLOR for future geometric rendering, but the color used to render the bitmap is unchanged.

To obtain the current raster color or index, you can use the query commands glGetFloatv() or glGetIntegerv() with GL_CURRENT_RASTER_COLOR or GL_CURRENT_RASTER_INDEX as the first argument.

Fonts and Display Lists

Display lists are discussed in general terms in Chapter 7. However, a few of the display-list management commands have special relevance for drawing strings of characters. As you read this section, keep in mind that the ideas presented here apply equally well to characters that are drawn using bitmap data and those drawn using geometric primitives (points, lines, and polygons). (See "Executing Multiple Display Lists" in Chapter 7 for an example of a geometric font.)

A font typically consists of a set of characters, where each character has an identifying number (usually the ASCII code) and a drawing method. For a standard ASCII character set, the capital letter A is number 65, B is 66, and so on. The string "DAB" would be represented by the three indices 68, 65, 66. In the simplest approach, display-list number 65 draws an A, number 66 draws a B, and so on. Then to draw the string 68, 65, 66, just execute the corresponding display lists.

You can use the command glCallLists() in just this way:

void glCallLists(GLsizei n, GLenum type, const GLvoid *lists);

The first argument, n, indicates the number of characters to be drawn, type is usually GL_BYTE, and lists is an array of character codes.

Since many applications need to draw character strings in multiple fonts and sizes, this simplest approach isn't convenient. Instead, you'd like to use 65 as A no matter what font is currently active. You could force font 1 to encode A, B, and C as 1065, 1066, 1067, and font 2 as 2065, 2066, 2067, but then any numbers larger than 256 would no longer fit in an 8-bit byte. A better solution is to add an offset to every entry in the string and to choose the display list. In this case, font 1 has A, B, and C represented by 1065, 1066, and 1067, and in font 2, they might be 2065, 2066, and 2067. Then to draw characters in font 1, set the offset to 1000 and draw display lists 65, 66, and 67. To draw that same string in font 2, set the offset to 2000 and draw the same lists.

To set the offset, use the command glListBase(). For the preceding examples, it should be called with 1000 or 2000 as the (only) argument. Now what you need is a contiguous list of unused display-list numbers, which you can obtain from glGenLists():

GLuint glGenLists(GLsizei range);

This function returns a block of range display-list identifiers. The returned lists are all marked as "used" even though they're empty, so that subsequent calls to glGenLists() never return the same lists (unless you've explicitly deleted them previously). Therefore, if you use 4 as the argument and if glGenLists() returns 81, you can use display-list identifiers 81, 82, 83, and 84 for your characters. If glGenLists() can't find a block of unused identifiers of the requested length, it returns 0. (Note that the command glDeleteLists() makes it easy to delete all the lists associated with a font in a single operation.)

Most American and European fonts have a small number of characters (fewer than 256), so it's easy to represent each character with a different code that can be stored in a single byte. Asian fonts, among others, may require much larger character sets, so a byte-per-character encoding is impossible. OpenGL allows strings to be composed of 1-, 2-, 3-, or 4-byte characters through the type parameter in glCallLists(). This parameter can have any of the following values:

GL_BYTE GL_UNSIGNED_BYTE

GL_SHORT GL_UNSIGNED_SHORT

GL_INT GL_UNSIGNED_INT

GL_FLOAT GL_2_BYTES

GL_3_BYTES GL_4_BYTES

(See "Executing Multiple Display Lists" in Chapter 7 for more information about these values.)

Defining and Using a Complete Font

The glBitmap() command and the display-list mechanism described in the previous section make it easy to define a raster font. In Example 8-2, the upper-case characters of an ASCII font are defined. In this example, each character has the same width, but this is not always the case. Once the characters are defined, the program prints the message "THE QUICK BROWN FOX JUMPS OVER A LAZY DOG".

The code in Example 8-2 is similar to the F example, except that each character's bitmap is stored in its own display list. The display list identifier, when combined with the offset returned by glGenLists(), is equal to the ASCII code for the character.

Example 8-2 : Drawing a Complete Font: font.c

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <stdlib.h>
#include <string.h>

GLubyte space[] =
    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
GLubyte letters[][13] = {
    {0x00, 0x00, 0xc3, 0xc3, 0xc3, 0xc3, 0xff, 0xc3, 0xc3, 0xc3, 0x66, 0x3c, 0x18},
    {0x00, 0x00, 0xfe, 0xc7, 0xc3, 0xc3, 0xc7, 0xfe, 0xc7, 0xc3, 0xc3, 0xc7, 0xfe},
    {0x00, 0x00, 0x7e, 0xe7, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xe7, 0x7e},
    {0x00, 0x00, 0xfc, 0xce, 0xc7, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc7, 0xce, 0xfc},
    {0x00, 0x00, 0xff, 0xc0, 0xc0, 0xc0, 0xc0, 0xfc, 0xc0, 0xc0, 0xc0, 0xc0, 0xff},
    {0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xfc, 0xc0, 0xc0, 0xc0, 0xff},
    {0x00, 0x00, 0x7e, 0xe7, 0xc3, 0xc3, 0xcf, 0xc0, 0xc0, 0xc0, 0xc0, 0xe7, 0x7e},
    {0x00, 0x00, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xff, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3},
    {0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e},
    {0x00, 0x00, 0x7c, 0xee, 0xc6, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06},
    {0x00, 0x00, 0xc3, 0xc6, 0xcc, 0xd8, 0xf0, 0xe0, 0xf0, 0xd8, 0xcc, 0xc6, 0xc3},
    {0x00, 0x00, 0xff, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0},
    {0x00, 0x00, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xdb, 0xff, 0xff, 0xe7, 0xc3},
    {0x00, 0x00, 0xc7, 0xc7, 0xcf, 0xcf, 0xdf, 0xdb, 0xfb, 0xf3, 0xf3, 0xe3, 0xe3},
    {0x00, 0x00, 0x7e, 0xe7, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xe7, 0x7e},
    {0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xfe, 0xc7, 0xc3, 0xc3, 0xc7, 0xfe},
    {0x00, 0x00, 0x3f, 0x6e, 0xdf, 0xdb, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0x66, 0x3c},
    {0x00, 0x00, 0xc3, 0xc6, 0xcc, 0xd8, 0xf0, 0xfe, 0xc7, 0xc3, 0xc3, 0xc7, 0xfe},
    {0x00, 0x00, 0x7e, 0xe7, 0x03, 0x03, 0x07, 0x7e, 0xe0, 0xc0, 0xc0, 0xe7, 0x7e},
    {0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff},
    {0x00, 0x00, 0x7e, 0xe7, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3},
    {0x00, 0x00, 0x18, 0x3c, 0x3c, 0x66, 0x66, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3},
    {0x00, 0x00, 0xc3, 0xe7, 0xff, 0xff, 0xdb, 0xdb, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3},
    {0x00, 0x00, 0xc3, 0x66, 0x66, 0x3c, 0x3c, 0x18, 0x3c, 0x3c, 0x66, 0x66, 0xc3},
    {0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x3c, 0x66, 0x66, 0xc3},
    {0x00, 0x00, 0xff, 0xc0, 0xc0, 0x60, 0x30, 0x7e, 0x0c, 0x06, 0x03, 0x03, 0xff}
};

GLuint fontOffset;

void makeRasterFont(void)
{
   GLuint i, j;
   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

   fontOffset = glGenLists (128);
   for (i = 0,j = `A'; i < 26; i++,j++) {
      glNewList(fontOffset + j, GL_COMPILE);
      glBitmap(8, 13, 0.0, 2.0, 10.0, 0.0, letters[i]);
      glEndList();
   }
   glNewList(fontOffset + ` `, GL_COMPILE);
   glBitmap(8, 13, 0.0, 2.0, 10.0, 0.0, space);
   glEndList();
}

void init(void)
{
   glShadeModel (GL_FLAT);
   makeRasterFont();
}

void printString(char *s)
{
   glPushAttrib (GL_LIST_BIT);
   glListBase(fontOffset);
   glCallLists(strlen(s), GL_UNSIGNED_BYTE, (GLubyte *) s);
   glPopAttrib ();
}

/* Everything above this line could be in a library
 * that defines a font.  To make it work, you've got
 * to call makeRasterFont() before you start making
 * calls to printString().
 */
void display(void)
{
   GLfloat white[3] = { 1.0, 1.0, 1.0 };

   glClear(GL_COLOR_BUFFER_BIT);
   glColor3fv(white);

   glRasterPos2i(20, 60);
   printString("THE QUICK BROWN FOX JUMPS");
   glRasterPos2i(20, 40);
   printString("OVER A LAZY DOG");
   glFlush ();
}

void reshape(int w, int h)
{
   glViewport(0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   glOrtho (0.0, w, 0.0, h, -1.0, 1.0);
   glMatrixMode(GL_MODELVIEW);
}

void keyboard(unsigned char key, int x, int y)
{
   switch (key) {
      case 27:
         exit(0);
   }
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
   glutInitWindowSize(300, 100);
   glutInitWindowPosition (100, 100);
   glutCreateWindow(argv[0]);
   init();
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyboard);
   glutDisplayFunc(display);
   glutMainLoop();
   return 0;
}

Images

An image is similar to a bitmap, but instead of containing only a single bit for each pixel in a rectangular region of the screen, an image can contain much more information. For example, an image can contain a complete (R, G, B, A) color stored at each pixel. Images can come from several sources, such as

  • A photograph that's digitized with a scanner
  • An image that was first generated on the screen by a graphics program using the graphics hardware and then read back, pixel by pixel
  • A software program that generated the image in memory pixel by pixel

The images you normally think of as pictures come from the color buffers. However, you can read or write rectangular regions of pixel data from or to the depth buffer or the stencil buffer. (See Chapter 10 for an explanation of these other buffers.)

In addition to simply being displayed on the screen, images can be used for texture maps, in which case they're essentially pasted onto polygons that are rendered on the screen in the normal way. (See Chapter 9 for more information about this technique.)

Reading, Writing, and Copying Pixel Data

OpenGL provides three basic commands that manipulate image data:

  • glReadPixels() - Reads a rectangular array of pixels from the framebuffer and stores the data in processor memory.
  • glDrawPixels() - Writes a rectangular array of pixels from data kept in processor memory into the framebuffer at the current raster position specified by glRasterPos*().
  • glCopyPixels() - Copies a rectangular array of pixels from one part of the framebuffer to another. This command behaves similarly to a call to glReadPixels() followed by a call to glDrawPixels(), but the data is never written into processor memory.

For the aforementioned commands, the order of pixel data processing operations is shown in Figure 8-3:

Figure 8-3 : Simplistic Diagram of Pixel Data Flow

The basic ideas in Figure 8-3 are correct. The coordinates of glRasterPos*(), which specify the current raster position used by glDrawPixels() and glCopyPixels(), are transformed by the geometric processing pipeline. Both glDrawPixels() and glCopyPixels() are affected by rasterization and per-fragment operations. (But when drawing or copying a pixel rectangle, there's almost never a reason to have fog or texture enabled.)

However, additional steps arise because there are many kinds of framebuffer data, many ways to store pixel information in computer memory, and various data conversions that can be performed during the reading, writing, and copying operations. These possibilities translate to many different modes of operation. If all your program does is copy images on the screen or read them into memory temporarily so that they can be copied out later, you can ignore most of these modes. However, if you want your program to modify the data while it's in memory - for example, if you have an image stored in one format but the window requires a different format - or if you want to save image data to a file for future restoration in another session or on another kind of machine with significantly different graphical capabilities, you have to understand the various modes.

The rest of this section describes the basic commands in detail. The following sections discuss the details of the series of imaging operations that comprise the Imaging Pipeline: pixel-storage modes, pixel-transfer operations, and pixel-mapping operations.

Reading Pixel Data from Frame Buffer to Processor Memory

void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height,
GLenum format, GLenum type, GLvoid *pixels);
Reads pixel data from the framebuffer rectangle whose lower-left corner is at (x, y) and whose dimensions are width and height and stores it in the array pointed to by pixels. format indicates the kind of pixel data elements that are read (an index value or an R, G, B, or A component value, as listed in Table 8-1), and type indicates the data type of each element (see Table 8-2).

If you are using glReadPixels() to obtain RGBA or color-index information, you may need to clarify which buffer you are trying to access. For example, if you have a double-buffered window, you need to specify whether you are reading data from the front buffer or back buffer. To control the current read source buffer, call glReadBuffer(). (See "Selecting Color Buffers for Writing and Reading" in Chapter 10.)

Table 8-1 : Pixel Formats for glReadPixels() or glDrawPixels()

format Constant | Pixel Format
GL_COLOR_INDEX | A single color index
GL_RGB | A red color component, followed by a green color component, followed by a blue color component
GL_RGBA | A red color component, followed by a green color component, followed by a blue color component, followed by an alpha color component
GL_RED | A single red color component
GL_GREEN | A single green color component
GL_BLUE | A single blue color component
GL_ALPHA | A single alpha color component
GL_LUMINANCE | A single luminance component
GL_LUMINANCE_ALPHA | A luminance component followed by an alpha color component
GL_STENCIL_INDEX | A single stencil index
GL_DEPTH_COMPONENT | A single depth component

Table 8-2 : Data Types for glReadPixels() or glDrawPixels()

type Constant | Data Type
GL_UNSIGNED_BYTE | unsigned 8-bit integer
GL_BYTE | signed 8-bit integer
GL_BITMAP | single bits in unsigned 8-bit integers using the same format as glBitmap()
GL_UNSIGNED_SHORT | unsigned 16-bit integer
GL_SHORT | signed 16-bit integer
GL_UNSIGNED_INT | unsigned 32-bit integer
GL_INT | signed 32-bit integer
GL_FLOAT | single-precision floating point

Remember that, depending on the format, anywhere from one to four elements are read (or written). For example, if the format is GL_RGBA and you're reading into 32-bit integers (that is, if type is equal to GL_UNSIGNED_INT or GL_INT), then every pixel read requires 16 bytes of storage (four components ´ four bytes/component).

Each element of the image is stored in memory as indicated by Table 8-2. If the element represents a continuous value, such as a red, green, blue, or luminance component, each value is scaled to fit into the available number of bits. For example, assume the red component is initially specified as a floating-point value between 0.0 and 1.0. If it needs to be packed into an unsigned byte, only 8 bits of precision are kept, even if more bits are allocated to the red component in the framebuffer. GL_UNSIGNED_SHORT and GL_UNSIGNED_INT give 16 and 32 bits of precision, respectively. The normal (signed) versions of GL_BYTE, GL_SHORT, and GL_INT have 7, 15, and 31 bits of precision, since the negative values are typically not used.

If the element is an index (a color index or a stencil index, for example), and the type is not GL_FLOAT, the value is simply masked against the available bits in the type. The signed versions - GL_BYTE, GL_SHORT, and GL_INT - have masks with one fewer bit. For example, if a color index is to be stored in a signed 8-bit integer, it's first masked against 0x7f. If the type is GL_FLOAT, the index is simply converted into a single-precision floating-point number (for example, the index 17 is converted to the float 17.0).

Writing Pixel Data from Processor Memory to Frame Buffer

void glDrawPixels(GLsizei width, GLsizei height, GLenum format,
GLenum type, const GLvoid *pixels);
Draws a rectangle of pixel data with dimensions width and height. The pixel rectangle is drawn with its lower-left corner at the current raster position. format and type have the same meaning as with glReadPixels(). (For legal values for format and type, see Table 8-1 and Table 8-2.) The array pointed to by pixels contains the pixel data to be drawn. If the current raster position is invalid, nothing is drawn, and the raster position remains invalid.

Example 8-3 is a portion of a program, which uses glDrawPixels() to draw an pixel rectangle in the lower-left corner of a window. makeCheckImage() creates a 64-by-64 RGB array of a black-and-white checkerboard image. glRasterPos2i(0,0) positions the lower-left corner of the image. For now, ignore glPixelStorei().

Example 8-3 : Use of glDrawPixels(): image.c

#define checkImageWidth 64
#define checkImageHeight 64
GLubyte checkImage[checkImageHeight][checkImageWidth][3];

void makeCheckImage(void)
{
   int i, j, c;

   for (i = 0; i < checkImageHeight; i++) {
      for (j = 0; j < checkImageWidth; j++) {
         c = ((((i&0x8)==0)^((j&0x8))==0))*255;
         checkImage[i][j][0] = (GLubyte) c;
         checkImage[i][j][1] = (GLubyte) c;
         checkImage[i][j][2] = (GLubyte) c;
      }
   }
}

void init(void)
{
   glClearColor (0.0, 0.0, 0.0, 0.0);
   glShadeModel(GL_FLAT);
   makeCheckImage();
   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
}
void display(void)
{
   glClear(GL_COLOR_BUFFER_BIT);
   glRasterPos2i(0, 0);
   glDrawPixels(checkImageWidth, checkImageHeight, GL_RGB,
                GL_UNSIGNED_BYTE, checkImage);
   glFlush();
}

When using glDrawPixels() to write RGBA or color-index information, you may need to control the current drawing buffers with glDrawBuffer(), which, along with glReadBuffer(), is also described in "Selecting Color Buffers for Writing and Reading" in Chapter 10.

Copying Pixel Data within the Frame Buffer

void glCopyPixels(GLint x, GLint y, GLsizei width, GLsizei height,
GLenum buffer);
Copies pixel data from the framebuffer rectangle whose lower-left corner is at (x, y) and whose dimensions are width and height. The data is copied to a new position whose lower-left corner is given by the current raster position. buffer is either GL_COLOR, GL_STENCIL, or GL_DEPTH, specifying the framebuffer that is used. glCopyPixels() behaves similarly to a glReadPixels() followed by a glDrawPixels(), with the following translation for the buffer to format parameter:
  • If buffer is GL_DEPTH or GL_STENCIL, then GL_DEPTH_COMPONENT or GL_STENCIL_INDEX is used, respectively.
  • If GL_COLOR is specified, GL_RGBA or GL_COLOR_INDEX is used, depending on whether the system is in RGBA or color-index mode.

Note that there's no need for a format or data parameter for glCopyPixels(), since the data is never copied into processor memory. The read source buffer and the destination buffer of glCopyPixels() are specified by glReadBuffer() and glDrawBuffer() respectively. Both glDrawPixels() and glCopyPixels() are used in Example 8-4.

For all three functions, the exact conversions of the data going to or from the framebuffer depend on the modes in effect at the time. See the next section for details.

Imaging Pipeline

This section discusses the complete Imaging Pipeline: the pixel-storage modes and pixel-transfer operations, which include how to set up an arbitrary mapping to convert pixel data. You can also magnify or reduce a pixel rectangle before it's drawn by calling glPixelZoom(). The order of these operations is shown in Figure 8-4.

Figure 8-4 : Imaging Pipeline

When glDrawPixels() is called, the data is first unpacked from processor memory according to the pixel-storage modes that are in effect and then the pixel-transfer operations are applied. The resulting pixels are then rasterized. During rasterization, the pixel rectangle may be zoomed up or down, depending on the current state. Finally, the fragment operations are applied and the pixels are written into the framebuffer. (See "Testing and Operating on Fragments" in Chapter 10 for a discussion of the fragment operations.)

When glReadPixels() is called, data is read from the framebuffer, the pixel-transfer operations are performed, and then the resulting data is packed into processor memory.

glCopyPixels() applies all the pixel-transfer operations during what would be the glReadPixels() activity. The resulting data is written as it would be by glDrawPixels(), but the transformations aren't applied a second time. Figure 8-5 shows how glCopyPixels() moves pixel data, starting from the frame buffer.

Figure 8-5 : glCopyPixels() Pixel Path

From "Drawing the Bitmap" and Figure 8-6, you see that rendering bitmaps is simpler than rendering images. Neither the pixel-transfer operations nor the pixel-zoom operation are applied.

Figure 8-6 : glBitmap() Pixel Path

Note that the pixel-storage modes and pixel-transfer operations are applied to textures as they are read from or written to texture memory. Figure 8-7 shows the effect on glTexImage*(), glTexSubImage*(), and glGetTexImage().

Figure 8-7 : glTexImage*(), glTexSubImage*(), and glGetTexImage() Pixel Paths

As seen in Figure 8-8, when pixel data is copied from the framebuffer into texture memory (glCopyTexImage*() or glCopyTexSubImage*()), only pixel-transfer operations are applied. (See Chapter 9 for more information on textures.)

Figure 8-8 : glCopyTexImage*() and glCopyTexSubImage*() Pixel Paths

Pixel Packing and Unpacking

Packing and unpacking refer to the way that pixel data is written to and read from processor memory.

An image stored in memory has between one and four chunks of data, called elements. The data might consist of just the color index or the luminance (luminance is the weighted sum of the red, green, and blue values), or it might consist of the red, green, blue, and alpha components for each pixel. The possible arrangements of pixel data, or formats, determine the number of elements stored for each pixel and their order.

Some elements (such as a color index or a stencil index) are integers, and others (such as the red, green, blue, and alpha components, or the depth component) are floating-point values, typically ranging between 0.0 and 1.0. Floating-point components are usually stored in the framebuffer with lower resolution than a full floating-point number would require (for example, color components may be stored in 8 bits). The exact number of bits used to represent the components depends on the particular hardware being used. Thus, it's often wasteful to store each component as a full 32-bit floating-point number, especially since images can easily contain a million pixels.

Elements can be stored in memory as various data types, ranging from 8-bit bytes to 32-bit integers or floating-point numbers. OpenGL explicitly defines the conversion of each component in each format to each of the possible data types. Keep in mind that you may lose data if you try to store a high-resolution component in a type represented by a small number of bits.

Controlling Pixel-Storage Modes

Image data is typically stored in processor memory in rectangular two- or three-dimensional arrays. Often, you want to display or store a subimage that corresponds to a subrectangle of the array. In addition, you might need to take into account that different machines have different byte-ordering conventions. Finally, some machines have hardware that is far more efficient at moving data to and from the framebuffer if the data is aligned on 2-byte, 4-byte, or 8-byte boundaries in processor memory. For such machines, you probably want to control the byte alignment. All the issues raised in this paragraph are controlled as pixel-storage modes, which are discussed in the next subsection. You specify these modes by using glPixelStore*(), which you've already seen used in a couple of example programs.

All the possible pixel-storage modes are controlled with the glPixelStore*() command. Typically, several successive calls are made with this command to set several parameter values.

void glPixelStore{if}(GLenum pname, TYPEparam);
Sets the pixel-storage modes, which affect the operation of glDrawPixels(), glReadPixels(), glBitmap(), glPolygonStipple(), glTexImage1D(), glTexImage2D(), glTexSubImage1D(), glTexSubImage2D(), and glGetTexImage(). The possible parameter names for pname are shown in Table 8-3, along with their data type, initial value, and valid range of values. The GL_UNPACK* parameters control how data is unpacked from memory by glDrawPixels(), glBitmap(), glPolygonStipple(), glTexImage1D(), glTexImage2D(), glTexSubImage1D(), and glTexSubImage2D(). The GL_PACK* parameters control how data is packed into memory by glReadPixels() and glGetTexImage().
Table 8-3 : glPixelStore() Parameters
Parameter Name | Type | Initial Value | Valid Range
GL_UNPACK_SWAP_BYTES, GL_PACK_SWAP_BYTES | GLboolean | FALSE | TRUE/FALSE
GL_UNPACK_LSB_FIRST, GL_PACK_LSB_FIRST | GLboolean | FALSE | TRUE/FALSE
GL_UNPACK_ROW_LENGTH, GL_PACK_ROW_LENGTH | GLint | 0 | any nonnegative integer
GL_UNPACK_SKIP_ROWS, GL_PACK_SKIP_ROWS | GLint | 0 | any nonnegative integer
GL_UNPACK_SKIP_PIXELS, GL_PACK_SKIP_PIXELS | GLint | 0 | any nonnegative integer
GL_UNPACK_ALIGNMENT, GL_PACK_ALIGNMENT | GLint | 4 | 1, 2, 4, 8

Since the corresponding parameters for packing and unpacking have the same meanings, they're discussed together in the rest of this section and referred to without the GL_PACK or GL_UNPACK prefix. For example, *SWAP_BYTES refers to GL_PACK_SWAP_BYTES and GL_UNPACK_SWAP_BYTES.

If the *SWAP_BYTES parameter is FALSE (the default), the ordering of the bytes in memory is whatever is native for the OpenGL client; otherwise, the bytes are reversed. The byte reversal applies to any size element, but really only has a meaningful effect for multibyte elements.

Note: As long as your OpenGL application doesn't share images with other machines, you can ignore the issue of byte ordering. If your application must render an OpenGL image that was created on a different machine and the "endianness" of the two machines differs, byte ordering can be swapped using *SWAP_BYTES. However, *SWAP_BYTES does not allow you to reorder elements (for example, to swap red and green).

The *LSB_FIRST parameter applies when drawing or reading 1-bit images or bitmaps, for which a single bit of data is saved or restored for each pixel. If *LSB_FIRST is FALSE (the default), the bits are taken from the bytes starting with the most significant bit; otherwise, they're taken in the opposite order. For example, if *LSB_FIRST is FALSE, and the byte in question is 0x31, the bits, in order, are {0, 0, 1, 1, 0, 0, 0, 1}. If *LSB_FIRST is TRUE, the order is {1, 0, 0, 0, 1, 1, 0, 0}.

Sometimes you want to draw or read only a subrectangle of the entire rectangle of image data stored in memory. If the rectangle in memory is larger than the subrectangle that's being drawn or read, you need to specify the actual length (measured in pixels) of the larger rectangle with *ROW_LENGTH. If *ROW_LENGTH is zero (which it is by default), the row length is understood to be the same as the width that's specified with glReadPixels(), glDrawPixels(), or glCopyPixels(). You also need to specify the number of rows and pixels to skip before starting to copy the data for the subrectangle. These numbers are set using the parameters *SKIP_ROWS and *SKIP_PIXELS, as shown in Figure 8-9. By default, both parameters are 0, so you start at the lower-left corner.

Figure 8-9 : *SKIP_ROWS, *SKIP_PIXELS, and *ROW_LENGTH Parameters

Often a particular machine's hardware is optimized for moving pixel data to and from memory, if the data is saved in memory with a particular byte alignment. For example, in a machine with 32-bit words, hardware can often retrieve data much faster if it's initially aligned on a 32-bit boundary, which typically has an address that is a multiple of 4. Likewise, 64-bit architectures might work better when the data is aligned to 8-byte boundaries. On some machines, however, byte alignment makes no difference.

As an example, suppose your machine works better with pixel data aligned to a 4-byte boundary. Images are most efficiently saved by forcing the data for each row of the image to begin on a 4-byte boundary. If the image is 5 pixels wide and each pixel consists of 1 byte each of red, green, and blue information, a row requires 5 ´ 3 = 15 bytes of data. Maximum display efficiency can be achieved if the first row, and each successive row, begins on a 4-byte boundary, so there is 1 byte of waste in the memory storage for each row. If your data is stored like this, set the *ALIGNMENT parameter appropriately (to 4, in this case).

If *ALIGNMENT is set to 1, the next available byte is used. If it's 2, a byte is skipped if necessary at the end of each row so that the first byte of the next row has an address that's a multiple of 2. In the case of bitmaps (or 1-bit images) where a single bit is saved for each pixel, the same byte alignment works, although you have to count individual bits. For example, if you're saving a single bit per pixel, the row length is 75, and the alignment is 4, then each row requires 75/8, or 9 3/8 bytes. Since 12 is the smallest multiple of 4 that is bigger than 9 3/8, 12 bytes of memory are used for each row. If the alignment is 1, then 10 bytes are used for each row, as 9 3/8 is rounded up to the next byte. (There is a simple use of glPixelStorei() in Example 8-4.)

Pixel-Transfer Operations

As image data is transferred from memory into the framebuffer, or from the framebuffer into memory, OpenGL can perform several operations on it. For example, the ranges of components can be altered - normally, the red component is between 0.0 and 1.0, but you might prefer to keep it in some other range; or perhaps the data you're using from a different graphics system stores the red component in a different range. You can even create maps to perform arbitrary conversion of color indices or color components during pixel transfer. Conversions such as these performed during the transfer of pixels to and from the framebuffer are called pixel-transfer operations. They're controlled with the glPixelTransfer*() and glPixelMap*() commands.

Be aware that although the color, depth, and stencil buffers have many similarities, they don't behave identically, and a few of the modes have special cases for special buffers. All the mode details are covered in this section and the sections that follow, including all the special cases.

Some of the pixel-transfer function characteristics are set with glPixelTransfer*(). The other characteristics are specified with glPixelMap*(), which is described in the next section.

void glPixelTransfer{if}(GLenum pname, TYPEparam);
Sets pixel-transfer modes that affect the operation of glDrawPixels(), glReadPixels(), glCopyPixels(), glTexImage1D(), glTexImage2D(), glCopyTexImage1D(), glCopyTexImage2D(), glTexSubImage1D(), glTexSubImage2D(), glCopyTexSubImage1D(), glCopyTexSubImage2D(), and glGetTexImage(). The parameter pname must be one of those listed in the first column of Table 8-4, and its value, param, must be in the valid range shown.
Table 8-4 : glPixelTransfer*() Parameters (continued)
Parameter Name | Type | Initial Value | Valid Range
GL_MAP_COLOR | GLboolean | FALSE | TRUE/FALSE
GL_MAP_STENCIL | GLboolean | FALSE | TRUE/FALSE
GL_INDEX_SHIFT | Glint | 0 | (- ¥ , ¥ )
GL_INDEX_OFFSET | Glint | 0 | (- ¥ , ¥ )
GL_RED_SCALE | Glfloat | 1.0 | (- ¥ , ¥ )
GL_GREEN_SCALE | Glfloat | 1.0 | (- ¥ , ¥ )
GL_BLUE_SCALE | Glfloat | 1.0 | (- ¥ , ¥ )
GL_ALPHA_SCALE | Glfloat | 1.0 | (- ¥ , ¥ )
GL_DEPTH_SCALE | Glfloat | 1.0 | (- ¥ , ¥ )
GL_RED_BIAS | Glfloat | 0 | (- ¥ , ¥ )
GL_GREEN_BIAS | Glfloat | 0 | (- ¥ , ¥ )
GL_BLUE_BIAS | Glfloat | 0 | (- ¥ , ¥ )
GL_ALPHA_BIAS | Glfloat | 0 | (- ¥ , ¥ )
GL_DEPTH_BIAS | Glfloat | 0 | (- ¥ , ¥ )

If the GL_MAP_COLOR or GL_MAP_STENCIL parameter is TRUE, then mapping is enabled. See the next subsection to learn how the mapping is done and how to change the contents of the maps. All the other parameters directly affect the pixel component values.

A scale and bias can be applied to the red, green, blue, alpha, and depth components. For example, you may wish to scale red, green, and blue components that were read from the framebuffer before converting them to a luminance format in processor memory. Luminance is computed as the sum of the red, green, and blue components, so if you use the default value for GL_RED_SCALE, GL_GREEN_SCALE and GL_BLUE_SCALE, the components all contribute equally to the final intensity or luminance value. If you want to convert RGB to luminance, according to the NTSC standard, you set GL_RED_SCALE to .30, GL_GREEN_SCALE to .59, and GL_BLUE_SCALE to .11.

Indices (color and stencil) can also be transformed. In the case of indices a shift and offset are applied. This is useful if you need to control which portion of the color table is used during rendering.

Pixel Mapping

All the color components, color indices, and stencil indices can be modified by means of a table lookup before they are placed in screen memory. The command for controlling this mapping is glPixelMap*().

void glPixelMap{ui us f}v(GLenum map, GLint mapsize,
const TYPE *values);
Loads the pixel map indicated by map with mapsize entries, whose values are pointed to by values. Table 8-5 lists the map names and values; the default sizes are all 1 and the default values are all 0. Each map's size must be a power of 2.
Table 8-5 : glPixelMap*() Parameter Names and Values
Map Name | Address | Value
GL_PIXEL_MAP_I_TO_I | color index | color index
GL_PIXEL_MAP_S_TO_S | stencil index | stencil index
GL_PIXEL_MAP_I_TO_R | color index | R
GL_PIXEL_MAP_I_TO_G | color index | G
GL_PIXEL_MAP_I_TO_B | color index | B
GL_PIXEL_MAP_I_TO_A | color index | A
GL_PIXEL_MAP_R_TO_R | R | R
GL_PIXEL_MAP_G_TO_G | G | G
GL_PIXEL_MAP_B_TO_B | B | B
GL_PIXEL_MAP_A_TO_A | A | A

The maximum size of the maps is machine-dependent. You can find the sizes of the pixel maps supported on your machine with glGetIntegerv(). Use the query argument GL_MAX_PIXEL_MAP_TABLE to obtain the maximum size for all the pixel map tables, and use GL_PIXEL_MAP_*_TO_*_SIZE to obtain the current size of the specified map. The six maps whose address is a color index or stencil index must always be sized to an integral power of 2. The four RGBA maps can be any size from 1 through GL_MAX_PIXEL_MAP_TABLE.

To understand how a table works, consider a simple example. Suppose that you want to create a 256-entry table that maps color indices to color indices using GL_PIXEL_MAP_I_TO_I. You create a table with an entry for each of the values between 0 and 255 and initialize the table with glPixelMap*(). Assume you're using the table for thresholding and want to map indices below 101 (indices 0 to 100) to 0, and all indices 101 and above to 255. In this case, your table consists of 101 0s and 155 255s. The pixel map is enabled using the routine glPixelTransfer*() to set the parameter GL_MAP_COLOR to TRUE. Once the pixel map is loaded and enabled, incoming color indices below 101 come out as 0, and incoming pixels between 101 and 255 are mapped to 255. If the incoming pixel is larger than 255, it's first masked by 255, throwing out all the bits above the eighth, and the resulting masked value is looked up in the table. If the incoming index is a floating-point value (say 88.14585), it's rounded to the nearest integer value (giving 88), and that number is looked up in the table (giving 0).

Using pixel maps, you can also map stencil indices or convert color indices to RGB. (See "Reading and Drawing Pixel Rectangles" for information about the conversion of indices.)

Magnifying, Reducing, or Flipping an Image

After the pixel-storage modes and pixel-transfer operations are applied, images and bitmaps are rasterized. Normally, each pixel in an image is written to a single pixel on the screen. However, you can arbitrarily magnify, reduce, or even flip (reflect) an image by using glPixelZoom().

void glPixelZoom(GLfloat zoom x, GLfloat zoom y);
Sets the magnification or reduction factors for pixel-write operations (glDrawPixels() or glCopyPixels()), in the x- and y-dimensions. By default, zoom x and zoom y are 1.0. If they're both 2.0, each image pixel is drawn to 4 screen pixels. Note that fractional magnification or reduction factors are allowed, as are negative factors. Negative zoom factors reflect the resulting image about the current raster position.

During rasterization, each image pixel is treated as a zoomx ´ zoomy rectangle, and fragments are generated for all the pixels whose centers lie within the rectangle. More specifically, let (xrp, yrp) be the current raster position. If a particular group of elements (index or components) is the nth in a row and belongs to the mth column, consider the region in window coordinates bounded by the rectangle with corners at

(xrp + zoomx * n, yrp + zoomy * m) and (xrp + zoomx(n+1), yrp + zoomy(m+1))

Any fragments whose centers lie inside this rectangle (or on its bottom or left boundaries) are produced in correspondence with this particular group of elements.

A negative zoom can be useful for flipping an image. OpenGL describes images from the bottom row of pixels to the top (and from left to right). If you have a "top to bottom" image, such as a frame of video, you may want to use glPixelZoom(1.0, -1.0) to make the image right side up for OpenGL. Be sure that you reposition the current raster position appropriately, if needed.

Example 8-4 shows the use of glPixelZoom(). A checkerboard image is initially drawn in the lower-left corner of the window. Pressing a mouse button and moving the mouse uses glCopyPixels() to copy the lower-left corner of the window to the current cursor location. (If you copy the image onto itself, it looks wacky!) The copied image is zoomed, but initially it is zoomed by the default value of 1.0, so you won't notice. The `z' and `Z' keys increase and decrease the zoom factors by 0.5. Any window damage causes the contents of the window to be redrawn. Pressing the `r' key resets the image and the zoom factors.

Example 8-4 : Drawing, Copying, and Zooming Pixel Data: image.c

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <stdlib.h>
#include <stdio.h>

#define checkImageWidth 64
#define checkImageHeight 64
GLubyte checkImage[checkImageHeight][checkImageWidth][3];

static GLdouble zoomFactor = 1.0;
static GLint height;

void makeCheckImage(void)
{
   int i, j, c;

   for (i = 0; i < checkImageHeight; i++) {
      for (j = 0; j < checkImageWidth; j++) {
         c = ((((i&0x8)==0)^((j&0x8))==0))*255;
         checkImage[i][j][0] = (GLubyte) c;
         checkImage[i][j][1] = (GLubyte) c;
         checkImage[i][j][2] = (GLubyte) c;
      }
   }
}

void init(void)
{
   glClearColor (0.0, 0.0, 0.0, 0.0);
   glShadeModel(GL_FLAT);
   makeCheckImage();
   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
}

void display(void)
{
   glClear(GL_COLOR_BUFFER_BIT);
   glRasterPos2i(0, 0);
   glDrawPixels(checkImageWidth, checkImageHeight, GL_RGB,
                GL_UNSIGNED_BYTE, checkImage);
   glFlush();
}

void reshape(int w, int h)
{
   glViewport(0, 0, (GLsizei) w, (GLsizei) h);
   height = (GLint) h;
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluOrtho2D(0.0, (GLdouble) w, 0.0, (GLdouble) h);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
}

void motion(int x, int y)
{
   static GLint screeny;

   screeny = height - (GLint) y;
   glRasterPos2i (x, screeny);
   glPixelZoom (zoomFactor, zoomFactor);
   glCopyPixels (0, 0, checkImageWidth, checkImageHeight,
                 GL_COLOR);
   glPixelZoom (1.0, 1.0);
   glFlush ();
}

void keyboard(unsigned char key, int x, int y)
{
   switch (key) {
      case `r':
      case `R':
         zoomFactor = 1.0;
         glutPostRedisplay();
         printf ("zoomFactor reset to 1.0\n");
         break;
      case `z':
         zoomFactor += 0.5;
         if (zoomFactor >= 3.0)
            zoomFactor = 3.0;
         printf ("zoomFactor is now %4.1f\n", zoomFactor);
         break;
      case `Z':
         zoomFactor -= 0.5;
         if (zoomFactor <= 0.5)
            zoomFactor = 0.5;
         printf ("zoomFactor is now %4.1f\n", zoomFactor);
         break;
      case 27:
         exit(0);
         break;
      default:
         break;
   }
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
   glutInitWindowSize(250, 250);
   glutInitWindowPosition(100, 100);
   glutCreateWindow(argv[0]);
   init();
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyboard);
   glutMotionFunc(motion);
   glutMainLoop();
   return 0;
}

Reading and Drawing Pixel Rectangles

This section describes the reading and drawing processes in detail. The pixel conversions performed when going from framebuffer to memory (reading) are similar but not identical to the conversions performed when going in the opposite direction (drawing), as explained in the following sections. You may wish to skip this section the first time through, especially if you do not plan to use the pixel-transfer operations right away.

The Pixel Rectangle Drawing Process

Figure 8-10 and the following list describe the operation of drawing pixels into the framebuffer.

Figure 8-10 : Drawing Pixels with glDrawPixels()

If the pixels aren't indices (that is, the format isn't GL_COLOR_INDEX or GL_STENCIL_INDEX), the first step is to convert the components to floating-point format if necessary. (See Table 4-1 for the details of the conversion.)

If the format is GL_LUMINANCE or GL_LUMINANCE_ALPHA, the luminance element is converted into R, G, and B, by using the luminance value for each of the R, G, and B components. In GL_LUMINANCE_ALPHA format, the alpha value becomes the A value. If GL_LUMINANCE is specified, the A value is set to 1.0.

Each component (R, G, B, A, or depth) is multiplied by the appropriate scale, and the appropriate bias is added. For example, the R component is multiplied by the value corresponding to GL_RED_SCALE and added to the value corresponding to GL_RED_BIAS.

If GL_MAP_COLOR is true, each of the R, G, B, and A components is clamped to the range [0.0,1.0], multiplied by an integer one less than the table size, truncated, and looked up in the table. (See "Tips for Improving Pixel Drawing Rates" for more details.)

Next, the R, G, B, and A components are clamped to [0.0,1.0], if they weren't already, and converted to fixed-point with as many bits to the left of the binary point as there are in the corresponding framebuffer component.

If you're working with index values (stencil or color indices), then the values are first converted to fixed-point (if they were initially floating-point numbers) with some unspecified bits to the right of the binary point. Indices that were initially fixed-point remain so, and any bits to the right of the binary point are set to zero.

The resulting index value is then shifted right or left by the absolute value of GL_INDEX_SHIFT bits; the value is shifted left if GL_INDEX_SHIFT > 0 and right otherwise. Finally, GL_INDEX_OFFSET is added to the index.

The next step with indices depends on whether you're using RGBA mode or color-index mode. In RGBA mode, a color index is converted to RGBA using the color components specified by GL_PIXEL_MAP_I_TO_R, GL_PIXEL_MAP_I_TO_G, GL_PIXEL_MAP_I_TO_B, and GL_PIXEL_MAP_I_TO_A. (See "Pixel Mapping" for details.) Otherwise, if GL_MAP_COLOR is GL_TRUE, a color index is looked up through the table GL_PIXEL_MAP_I_TO_I. (If GL_MAP_COLOR is GL_FALSE, the index is unchanged.) If the image is made up of stencil indices rather than color indices, and if GL_MAP_STENCIL is GL_TRUE, the index is looked up in the table corresponding to GL_PIXEL_MAP_S_TO_S. If GL_MAP_STENCIL is FALSE, the stencil index is unchanged.

Finally, if the indices haven't been converted to RGBA, the indices are then masked to the number of bits of either the color-index or stencil buffer, whichever is appropriate.

The Pixel Rectangle Reading Process

Many of the conversions done during the pixel rectangle drawing process are also done during the pixel rectangle reading process. The pixel reading process is shown in Figure 8-11 and described in the following list.

Figure 8-11 : Reading Pixels with glReadPixels()

If the pixels to be read aren't indices (that is, the format isn't GL_COLOR_INDEX or GL_STENCIL_INDEX), the components are mapped to [0.0,1.0] - that is, in exactly the opposite way that they are when written.

Next, the scales and biases are applied to each component. If GL_MAP_COLOR is GL_TRUE, they're mapped and again clamped to [0.0,1.0]. If luminance is desired instead of RGB, the R, G, and B components are added (L = R + G + B).

If the pixels are indices (color or stencil), they're shifted, offset, and, if GL_MAP_COLOR is GL_TRUE, also mapped.

If the storage format is either GL_COLOR_INDEX or GL_STENCIL_INDEX, the pixel indices are masked to the number of bits of the storage type (1, 8, 16, or 32) and packed into memory as previously described.

If the storage format is one of the component kind (such as luminance or RGB), the pixels are always mapped by the index-to-RGBA maps. Then, they're treated as though they had been RGBA pixels in the first place (including potential conversion to luminance).

Finally, for both index and component data, the results are packed into memory according to the GL_PACK* modes set with glPixelStore*().

The scaling, bias, shift, and offset values are the same as those used when drawing pixels, so if you're both reading and drawing pixels, be sure to reset these components to the appropriate values before doing a read or a draw. Similarly, the various maps must be properly reset if you intend to use maps for both reading and drawing.

Note: It might seem that luminance is handled incorrectly in both the reading and drawing operations. For example, luminance is not usually equally dependent on the R, G, and B components as it may be assumed from both Figure 8-10 and Figure 8-11. If you wanted your luminance to be calculated such that the R component contributed 30 percent, the G 59 percent, and the B 11 percent, you can set GL_RED_SCALE to .30, GL_RED_BIAS to 0.0, and so on. The computed L is then .30R + .59G + .11B.

Tips for Improving Pixel Drawing Rates

As you can see, OpenGL has a rich set of features for reading, drawing and manipulating pixel data. Although these features are often very useful, they can also decrease performance. Here are some tips for improving pixel draw rates.

  • For best performance, set all pixel-transfer parameters to their default values, and set pixel zoom to (1.0,1.0).
  • A series of fragment operations is applied to pixels as they are drawn into the framebuffer. (See "Testing and Operating on Fragments" in Chapter 10.) For optimum performance disable all fragment operations.
  • While performing pixel operations, disable other costly states, such as texturing and lighting.
  • If you use an image format and type that matches the framebuffer, you can reduce the amount of work that the OpenGL implementation has to do. For example, if you are writing images to an RGB framebuffer with 8 bits per component, call glDrawPixels() with format set to RGB and type set to UNSIGNED_BYTE.
  • For some implementations, unsigned image formats are faster to use than signed image formats.
  • It is usually faster to draw a large pixel rectangle than to draw several small ones, since the cost of transferring the pixel data can be amortized over many pixels.
  • If possible, reduce the amount of data that needs to be copied by using small data types (for example, use GL_UNSIGNED_BYTE) and fewer components (for example, use format GL_LUMINANCE_ALPHA).
  • Pixel-transfer operations, including pixel mapping and values for scale, bias, offset, and shift other than the defaults, may decrease performance.


Chapter 9 - OpenGL Programming Guide

OpenGL Programming Guide
Programming Guide > Chapter 9

Chapter 9
Texture Mapping

Chapter Objectives

After reading this chapter, you'll be able to do the following:

  • Understand what texture mapping can add to your scene
  • Specify a texture image
  • Control how a texture image is filtered as it's applied to a fragment
  • Create and manage texture images in texture objects and, if available, control a high-performance working set of those texture objects
  • Specify how the color values in the image combine with those of the fragment to which it's being applied
  • Supply texture coordinates to indicate how the texture image should be aligned to the objects in your scene
  • Use automatic texture coordinate generation to produce effects like contour maps and environment maps

So far, every geometric primitive has been drawn as either a solid color or smoothly shaded between the colors at its vertices - that is, they've been drawn without texture mapping. If you want to draw a large brick wall without texture mapping, for example, each brick must be drawn as a separate polygon. Without texturing, a large flat wall - which is really a single rectangle - might require thousands of individual bricks, and even then the bricks may appear too smooth and regular to be realistic.

Texture mapping allows you to glue an image of a brick wall (obtained, perhaps, by scanning in a photograph of a real wall) to a polygon and to draw the entire wall as a single polygon. Texture mapping ensures that all the right things happen as the polygon is transformed and rendered. For example, when the wall is viewed in perspective, the bricks may appear smaller as the wall gets farther from the viewpoint. Other uses for texture mapping include depicting vegetation on large polygons representing the ground in flight simulation; wallpaper patterns; and textures that make polygons look like natural substances such as marble, wood, or cloth. The possibilities are endless. Although it's most natural to think of applying textures to polygons, textures can be applied to all primitives - points, lines, polygons, bitmaps, and images. Plates 6, 8, 1821, 2427, and 2931 all demonstrate the use of textures.

Because there are so many possibilities, texture mapping is a fairly large, complex subject, and you must make several programming choices when using it. For instance, you can map textures to surfaces made of a set of polygons or to curved surfaces, and you can repeat a texture in one or both directions to cover the surface. A texture can even be one-dimensional. In addition, you can automatically map a texture onto an object in such a way that the texture indicates contours or other properties of the item being viewed. Shiny objects can be textured so that they appear to be in the center of a room or other environment, reflecting the surroundings off their surfaces. Finally, a texture can be applied to a surface in different ways. It can be painted on directly (like a decal placed on a surface), used to modulate the color the surface would have been painted otherwise, or used to blend a texture color with the surface color. If this is your first exposure to texture mapping, you might find that the discussion in this chapter moves fairly quickly. As an additional reference, you might look at the chapter on texture mapping in Fundamentals of Three-Dimensional Computer Graphics by Alan Watt (Reading, MA: Addison-Wesley Publishing Company, 1990).

Textures are simply rectangular arrays of data - for example, color data, luminance data, or color and alpha data. The individual values in a texture array are often called texels. What makes texture mapping tricky is that a rectangular texture can be mapped to nonrectangular regions, and this must be done in a reasonable way.

Figure 9-1 illustrates the texture-mapping process. The left side of the figure represents the entire texture, and the black outline represents a quadrilateral shape whose corners are mapped to those spots on the texture. When the quadrilateral is displayed on the screen, it might be distorted by applying various transformations - rotations, translations, scaling, and projections. The right side of the figure shows how the texture-mapped quadrilateral might appear on your screen after these transformations. (Note that this quadrilateral is concave and might not be rendered correctly by OpenGL without prior tessellation. See Chapter 11 for more information about tessellating polygons.)

Figure 9-1 : Texture-Mapping Process

Notice how the texture is distorted to match the distortion of the quadrilateral. In this case, it's stretched in the x direction and compressed in the y direction; there's a bit of rotation and shearing going on as well. Depending on the texture size, the quadrilateral's distortion, and the size of the screen image, some of the texels might be mapped to more than one fragment, and some fragments might be covered by multiple texels. Since the texture is made up of discrete texels (in this case, 256 ´ 256 of them), filtering operations must be performed to map texels to fragments. For example, if many texels correspond to a fragment, they're averaged down to fit; if texel boundaries fall across fragment boundaries, a weighted average of the applicable texels is performed. Because of these calculations, texturing is computationally expensive, which is why many specialized graphics systems include hardware support for texture mapping.

An application may establish texture objects, with each texture object representing a single texture (and possible associated mipmaps). Some implementations of OpenGL can support a special working set of texture objects that have better performance than texture objects outside the working set. These high-performance texture objects are said to be resident and may have special hardware and/or software acceleration available. You may use OpenGL to create and delete texture objects and to determine which textures constitute your working set.

This chapter covers the OpenGL's texture-mapping facility in the following major sections.

  • "An Overview and an Example" gives a brief, broad look at the steps required to perform texture mapping. It also presents a relatively simple example of texture mapping.
  • "Specifying the Texture" explains how to specify one- or two-dimensional textures. It also discusses how to use a texture's borders, how to supply a series of related textures of different sizes, and how to control the filtering methods used to determine how an applied texture is mapped to screen coordinates.
  • "Filtering" details how textures are either magnified or minified as they are applied to the pixels of polygons. Minification using special mipmap textures is also explained.
  • "Texture Objects" describes how to put texture images into objects so that you can control several textures at one time. With texture objects, you may be able to create a working set of high-performance textures, which are said to be resident. You may also prioritize texture objects to increase or decrease the likelihood that a texture object is resident.
  • "Texture Functions" discusses the methods used for painting a texture onto a surface. You can choose to have the texture color values replace those that would be used if texturing wasn't in effect, or you can have the final color be a combination of the two.
  • "Assigning Texture Coordinates" describes how to compute and assign appropriate texture coordinates to the vertices of an object. It also explains how to control the behavior of coordinates that lie outside the default range - that is, how to repeat or clamp textures across a surface.
  • "Automatic Texture-Coordinate Generation" shows how to have OpenGL automatically generate texture coordinates so that you can achieve such effects as contour and environment maps.
  • "Advanced Features" explains how to manipulate the texture matrix stack and how to use the q texture coordinate.

Version 1.1 of OpenGL introduces several new texture-mapping operations:

    • Thirty-eight additional internal texture image formats
    • Texture proxy, to query whether there are enough resources to accommodate a given texture image
    • Texture subimage, to replace all or part of an existing texture image rather than completely deleting and creating a texture to achieve the same effect
    • Specifying texture data from framebuffer memory (as well as from processor memory)
    • Texture objects, including resident textures and prioritizing

If you try to use one of these texture-mapping operations and can't find it, check the version number of your implementation of OpenGL to see if it actually supports it. (See "Which Version Am I Using?" in Chapter 14.)

An Overview and an Example

This section gives an overview of the steps necessary to perform texture mapping. It also presents a relatively simple texture-mapping program. Of course, you know that texture mapping can be a very involved process.

Steps in Texture Mapping

To use texture mapping, you perform these steps.

Create a texture object and specify a texture for that object.

Indicate how the texture is to be applied to each pixel.

Enable texture mapping.

Draw the scene, supplying both texture and geometric coordinates.

Keep in mind that texture mapping works only in RGBA mode. Texture mapping results in color-index mode are undefined.

Create a Texture Object and Specify a Texture for That Object

A texture is usually thought of as being two-dimensional, like most images, but it can also be one-dimensional. The data describing a texture may consist of one, two, three, or four elements per texel, representing anything from a modulation constant to an (R, G, B, A) quadruple.

In Example 9-1, which is very simple, a single texture object is created to maintain a single two-dimensional texture. This example does not find out how much memory is available. Since only one texture is created, there is no attempt to prioritize or otherwise manage a working set of texture objects. Other advanced techniques, such as texture borders or mipmaps, are not used in this simple example.

Indicate How the Texture Is to Be Applied to Each Pixel

You can choose any of four possible functions for computing the final RGBA value from the fragment color and the texture-image data. One possibility is simply to use the texture color as the final color; this is the decal mode, in which the texture is painted on top of the fragment, just as a decal would be applied. (Example 9-1 uses decal mode.) The replace mode, a variant of the decal mode, is a second method. Another method is to use the texture to modulate, or scale, the fragment's color; this technique is useful for combining the effects of lighting with texturing. Finally, a constant color can be blended with that of the fragment, based on the texture value.

Enable Texture Mapping

You need to enable texturing before drawing your scene. Texturing is enabled or disabled using glEnable() or glDisable() with the symbolic constant GL_TEXTURE_1D or GL_TEXTURE_2D for one- or two-dimensional texturing, respectively. (If both are enabled, GL_TEXTURE_2D is the one that is used.)

Draw the Scene, Supplying Both Texture and Geometric Coordinates

You need to indicate how the texture should be aligned relative to the fragments to which it's to be applied before it's "glued on." That is, you need to specify both texture coordinates and geometric coordinates as you specify the objects in your scene. For a two-dimensional texture map, for example, the texture coordinates range from 0.0 to 1.0 in both directions, but the coordinates of the items being textured can be anything. For the brick-wall example, if the wall is square and meant to represent one copy of the texture, the code would probably assign texture coordinates (0, 0), (1, 0), (1, 1), and (0, 1) to the four corners of the wall. If the wall is large, you might want to paint several copies of the texture map on it. If you do so, the texture map must be designed so that the bricks on the left edge match up nicely with the bricks on the right edge, and similarly for the bricks on the top and those on the bottom.

You must also indicate how texture coordinates outside the range [0.0,1.0] should be treated. Do the textures repeat to cover the object, or are they clamped to a boundary value?

A Sample Program

One of the problems with showing sample programs to illustrate texture mapping is that interesting textures are large. Typically, textures are read from an image file, since specifying a texture programmatically could take hundreds of lines of code. In Example 9-1, the texture - which consists of alternating white and black squares, like a checkerboard - is generated by the program. The program applies this texture to two squares, which are then rendered in perspective, one of them facing the viewer squarely and the other tilting back at 45 degrees, as shown in Figure 9-2. In object coordinates, both squares are the same size.

Figure 9-2 : Texture-Mapped Squares

Example 9-1 : Texture-Mapped Checkerboard: checker.c

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <stdlib.h>
#include <stdio.h>

/*  Create checkerboard texture  */
#define checkImageWidth 64
#define checkImageHeight 64
static GLubyte checkImage[checkImageHeight][checkImageWidth][4];

static GLuint texName;

void makeCheckImage(void)
{
   int i, j, c;

   for (i = 0; i < checkImageHeight; i++) {
      for (j = 0; j < checkImageWidth; j++) {
         c = ((((i&0x8)==0)^((j&0x8))==0))*255;
         checkImage[i][j][0] = (GLubyte) c;
         checkImage[i][j][1] = (GLubyte) c;
         checkImage[i][j][2] = (GLubyte) c;
         checkImage[i][j][3] = (GLubyte) 255;
      }
   }
}

void init(void)
{
   glClearColor (0.0, 0.0, 0.0, 0.0);
   glShadeModel(GL_FLAT);
   glEnable(GL_DEPTH_TEST);

   makeCheckImage();
   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

   glGenTextures(1, &texName);
   glBindTexture(GL_TEXTURE_2D, texName);

   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
                   GL_NEAREST);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
                   GL_NEAREST);
   glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, checkImageWidth,
                checkImageHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                checkImage);
}

void display(void)
{
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   glEnable(GL_TEXTURE_2D);
   glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
   glBindTexture(GL_TEXTURE_2D, texName);
   glBegin(GL_QUADS);
   glTexCoord2f(0.0, 0.0); glVertex3f(-2.0, -1.0, 0.0);
   glTexCoord2f(0.0, 1.0); glVertex3f(-2.0, 1.0, 0.0);
   glTexCoord2f(1.0, 1.0); glVertex3f(0.0, 1.0, 0.0);
   glTexCoord2f(1.0, 0.0); glVertex3f(0.0, -1.0, 0.0);

   glTexCoord2f(0.0, 0.0); glVertex3f(1.0, -1.0, 0.0);
   glTexCoord2f(0.0, 1.0); glVertex3f(1.0, 1.0, 0.0);
   glTexCoord2f(1.0, 1.0); glVertex3f(2.41421, 1.0, -1.41421);
   glTexCoord2f(1.0, 0.0); glVertex3f(2.41421, -1.0, -1.41421);
   glEnd();
   glFlush();
   glDisable(GL_TEXTURE_2D);
}

void reshape(int w, int h)
{
   glViewport(0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluPerspective(60.0, (GLfloat) w/(GLfloat) h, 1.0, 30.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
   glTranslatef(0.0, 0.0, -3.6);
}

void keyboard (unsigned char key, int x, int y)
{
   switch (key) {
      case 27:
         exit(0);
         break;
      default:
         break;
   }
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);
   glutInitWindowSize(250, 250);
   glutInitWindowPosition(100, 100);
   glutCreateWindow(argv[0]);
   init();
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyboard);
   glutMainLoop();
   return 0;
}

The checkerboard texture is generated in the routine makeCheckImage(), and all the texture-mapping initialization occurs in the routine init(). glGenTextures() and glBindTexture() name and create a texture object for a texture image. (See "Texture Objects.") The single, full-resolution texture map is specified by glTexImage2D(), whose parameters indicate the size of the image, type of the image, location of the image, and other properties of it. (See "Specifying the Texture" for more information about glTexImage2D().)

The four calls to glTexParameter*() specify how the texture is to be wrapped and how the colors are to be filtered if there isn't an exact match between pixels in the texture and pixels on the screen. (See "Repeating and Clamping Textures" and "Filtering.")

In display(), glEnable() turns on texturing. glTexEnv*() sets the drawing mode to GL_DECAL so that the textured polygons are drawn using the colors from the texture map (rather than taking into account what color the polygons would have been drawn without the texture).

Then, two polygons are drawn. Note that texture coordinates are specified along with vertex coordinates. The glTexCoord*() command behaves similarly to the glNormal() command. glTexCoord*() sets the current texture coordinates; any subsequent vertex command has those texture coordinates associated with it until glTexCoord*() is called again.

Note: The checkerboard image on the tilted polygon might look wrong when you compile and run it on your machine - for example, it might look like two triangles with different projections of the checkerboard image on them. If so, try setting the parameter GL_PERSPECTIVE_CORRECTION_HINT to GL_NICEST and running the example again. To do this, use glHint().

Specifying the Texture

The command glTexImage2D() defines a two-dimensional texture. It takes several arguments, which are described briefly here and in more detail in the subsections that follow. The related command for one-dimensional textures, glTexImage1D(), is described in "One-Dimensional Textures."

void glTexImage2D(GLenum target, GLint level, GLint internalFormat,
GLsizei width, GLsizei height, GLint border,
GLenum format, GLenum type,
const GLvoid *pixels);
Defines a two-dimensional texture. The target parameter is set to either the constant GL_TEXTURE_2D or GL_PROXY_TEXTURE_2D. You use the level parameter if you're supplying multiple resolutions of the texture map; with only one resolution, level should be 0. (See "Multiple Levels of Detail" for more information about using multiple resolutions.)
The next parameter, internalFormat, indicates which of the R, G, B, and A components or luminance or intensity values are selected for use in describing the texels of an image. The value of internalFormat is an integer from 1 to 4, or one of thirty-eight symbolic constants. The thirty-eight symbolic constants that are also legal values for internalFormat are GL_ALPHA, GL_ALPHA4, GL_ALPHA8, GL_ALPHA12, GL_ALPHA16, GL_LUMINANCE, GL_LUMINANCE4, GL_LUMINANCE8, GL_LUMINANCE12, GL_LUMINANCE16, GL_LUMINANCE_ALPHA, GL_LUMINANCE4_ALPHA4, GL_LUMINANCE6_ALPHA2, GL_LUMINANCE8_ALPHA8, GL_LUMINANCE12_ALPHA4, GL_LUMINANCE12_ALPHA12, GL_LUMINANCE16_ALPHA16, GL_INTENSITY, GL_INTENSITY4, GL_INTENSITY8, GL_INTENSITY12, GL_INTENSITY16, GL_RGB, GL_R3_G3_B2, GL_RGB4, GL_RGB5, GL_RGB8, GL_RGB10, GL_RGB12, GL_RGB16, GL_RGBA, GL_RGBA2, GL_RGBA4, GL_RGB5_A1, GL_RGBA8, GL_RGB10_A2, GL_RGBA12, and GL_RGBA16. (See "Texture Functions" for a discussion of how these selected components are applied.)
If internalFormat is one of the thirty-eight symbolic constants, then you are asking for specific components and perhaps the resolution of those components. For example, if internalFormat is GL_R3_G3_B2, you are asking that texels be 3 bits of red, 3 bits of green, and 2 bits of blue, but OpenGL is not guaranteed to deliver this. OpenGL is only obligated to choose an internal representation that closely approximates what is requested, but an exact match is usually not required. By definition, GL_LUMINANCE, GL_LUMINANCE_ALPHA, GL_RGB, and GL_RGBA are lenient, because they do not ask for a specific resolution. (For compatibility with the OpenGL release 1.0, the numeric values 1, 2, 3, and 4, for internalFormat, are equivalent to the symbolic constants GL_LUMINANCE, GL_LUMINANCE_ALPHA, GL_RGB, and GL_RGBA, respectively.)
The width and height parameters give the dimensions of the texture image; border indicates the width of the border, which is either zero (no border) or one. (See "Using a Texture's Borders" .) Both width and height must have the form 2m+2b, where m is a nonnegative integer (which can have a different value for width than for height) and b is the value of border. The maximum size of a texture map depends on the implementation of OpenGL, but it must be at least 64 ´ 64 (or 66 ´ 66 with borders).
The format and type parameters describe the format and data type of the texture image data. They have the same meaning as they do for glDrawPixels(). (See "Imaging Pipeline" in Chapter 8 .) In fact, texture data is in the same format as the data used by glDrawPixels(), so the settings of glPixelStore*() and glPixelTransfer*() are applied. (In Example 9-1, the call
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

is made because the data in the example isn't padded at the end of each texel row.) The format parameter can be GL_COLOR_INDEX, GL_RGB, GL_RGBA, GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_LUMINANCE, or GL_LUMINANCE_ALPHA - that is, the same formats available for glDrawPixels() with the exceptions of GL_STENCIL_INDEX and GL_DEPTH_COMPONENT.
Similarly, the type parameter can be GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT, GL_FLOAT, or GL_BITMAP.
Finally, pixels contains the texture-image data. This data describes the texture image itself as well as its border.

The internal format of a texture image may affect the performance of texture operations. For example, some implementations perform texturing with GL_RGBA faster than GL_RGB, because the color components align the processor memory better. Since this varies, you should check specific information about your implementation of OpenGL.

The internal format of a texture image also may control how much memory a texture image consumes. For example, a texture of internal format GL_RGBA8 uses 32 bits per texel, while a texture of internal format GL_R3_G3_B2 only uses 8 bits per texel. Of course, there is a corresponding trade-off between memory consumption and color resolution.

Note: Although texture-mapping results in color-index mode are undefined, you can still specify a texture with a GL_COLOR_INDEX image. In that case, pixel-transfer operations are applied to convert the indices to RGBA values by table lookup before they're used to form the texture image.

The number of texels for both the width and height of a texture image, not including the optional border, must be a power of 2. If your original image does not have dimensions that fit that limitation, you can use the OpenGL Utility Library routine gluScaleImage() to alter the size of your textures.

int gluScaleImage(GLenum format, GLint widthin, GLint heightin,
GLenum typein, const void *datain, GLint widthout,
GLint heightout, GLenum typeout, void *dataout);
Scales an image using the appropriate pixel-storage modes to unpack the data from datain. The format, typein, and typeout parameters can refer to any of the formats or data types supported by glDrawPixels(). The image is scaled using linear interpolation and box filtering (from the size indicated by widthin and heightin to widthout and heightout), and the resulting image is written to dataout, using the pixel GL_PACK* storage modes. The caller of gluScaleImage() must allocate sufficient space for the output buffer. A value of 0 is returned on success, and a GLU error code is returned on failure.

The framebuffer itself can also be used as a source for texture data. glCopyTexImage2D() reads a rectangle of pixels from the framebuffer and uses it for a new texture.

void glCopyTexImage2D(GLenum target, GLint level,
GLint internalFormat,
GLint x, GLint y, GLsizei width, GLsizei height,
GLint border);
Creates a two-dimensional texture, using framebuffer data to define the texels. The pixels are read from the current GL_READ_BUFFER and are processed exactly as if glCopyPixels() had been called but stopped before final conversion. The settings of glPixelTransfer*() are applied.
The target parameter must be set to the constant GL_TEXTURE_2D. The level, internalFormat, and border parameters have the same effects that they have for glTexImage2D(). The texture array is taken from a screen-aligned pixel rectangle with the lower-left corner at coordinates specified by the (x, y) parameters. The width and height parameters specify the size of this pixel rectangle. Both width and height must have the form 2m+2b, where m is a nonnegative integer (which can have a different value for width than for height) and b is the value of border.

The next sections give more detail about texturing, including the use of the target, border, and level parameters. The target parameter can be used to accurately query the size of a texture (by creating a texture proxy with glTexImage*D()) and whether a texture possibly can be used within the texture resources of an OpenGL implementation. Redefining a portion of a texture is described in "Replacing All or Part of a Texture Image." One-dimensional textures are discussed in "One-Dimensional Textures." The texture border, which has its size controlled by the border parameter, is detailed in "Using a Texture's Borders." The level parameter is used to specify textures of different resolutions and is incorporated into the special technique of mipmapping, which is explained in "Multiple Levels of Detail." Mipmapping requires understanding how to filter textures as they're applied; filtering is the subject of "Filtering."

Texture Proxy

To an OpenGL programmer who uses textures, size is important. Texture resources are typically limited and vary among OpenGL implementations. There is a special texture proxy target to evaluate whether sufficient resources are available.

glGetIntegerv(GL_MAX_TEXTURE_SIZE,...) tells you the largest dimension (width or height, without borders) of a texture image, typically the size of the largest square texture supported. However, GL_MAX_TEXTURE_SIZE does not consider the effect of the internal format of a texture. A texture image that stores texels using the GL_RGBA16 internal format may be using 64 bits per texel, so its image may have to be 16 times smaller than an image with the GL_LUMINANCE4 internal format. (Also, images requiring borders or mipmaps may further reduce the amount of available memory.)

A special place holder, or proxy, for a texture image allows the program to query more accurately whether OpenGL can accommodate a texture of a desired internal format. To use the proxy to query OpenGL, call glTexImage2D() with a target parameter of GL_PROXY_TEXTURE_2D and the given level, internalFormat, width, height, border, format, and type. (For one-dimensional textures, use corresponding 1D routines and symbolic constants.) For a proxy, you should pass NULL as the pointer for the pixels array.

To find out whether there are enough resources available for your texture, after the texture proxy has been created, query the texture state variables with glGetTexLevelParameter*(). If there aren't enough resources to accommodate the texture proxy, the texture state variables for width, height, border width, and component resolutions are set to 0.

void glGetTexLevelParameter{if}v(GLenum target, GLint level,
GLenum pname, TYPE *params);
Returns in params texture parameter values for a specific level of detail, specified as level. target defines the target texture and is one of GL_TEXTURE_1D, GL_TEXTURE_2D, GL_PROXY_TEXTURE_1D, or GL_PROXY_TEXTURE_2D. Accepted values for pname are GL_TEXTURE_WIDTH, GL_TEXTURE_HEIGHT, GL_TEXTURE_BORDER, GL_TEXTURE_INTERNAL_FORMAT, GL_TEXTURE_RED_SIZE, GL_TEXTURE_GREEN_SIZE, GL_TEXTURE_BLUE_SIZE, GL_TEXTURE_ALPHA_SIZE, GL_TEXTURE_LUMINANCE_SIZE, or GL_TEXTURE_INTENSITY_SIZE.
GL_TEXTURE_COMPONENTS is also accepted for pname, but only for backward compatibility with OpenGL Release 1.0 - GL_TEXTURE_INTERNAL_FORMAT is the recommended symbolic constant for Release 1.1.

Example 9-2 demonstrates how to use the texture proxy to find out if there are enough resources to create a 64 ´ 64 texel texture with RGBA components with 8 bits of resolution. If this succeeds, then glGetTexLevelParameteriv() stores the internal format (in this case, GL_RGBA8) into the variable format.

Example 9-2 : Querying Texture Resources with a Texture Proxy

  GLint format;

  glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA8,
               64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
  glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0,
                           GL_TEXTURE_INTERNAL_FORMAT, &format);

Note: There is one major limitation about texture proxies: The texture proxy tells you if there is space for your texture, but only if all texture resources are available (in other words, if it's the only texture in town). If other textures are using resources, then the texture proxy query may respond affirmatively, but there may not be enough space to make your texture resident (that is, part of a possibly high-performance working set of textures). (See "Texture Objects" for more information about managing resident textures.)

Replacing All or Part of a Texture Image

Creating a texture may be more computationally expensive than modifying an existing one. In OpenGL Release 1.1, there are new routines to replace all or part of a texture image with new information. This can be helpful for certain applications, such as using real-time, captured video images as texture images. For that application, it makes sense to create a single texture and use glTexSubImage2D() to repeatedly replace the texture data with new video images. Also, there are no size restrictions for glTexSubImage2D() that force the height or width to be a power of two. This is helpful for processing video images, which generally do not have sizes that are powers of two.

void glTexSubImage2D(GLenum target, GLint level, GLint xoffset,
GLint yoffset, GLsizei width, GLsizei height,
GLenum format, GLenum type, const GLvoid *pixels);
Defines a two-dimensional texture image that replaces all or part of a contiguous subregion (in 2D, it's simply a rectangle) of the current, existing two-dimensional texture image. The target parameter must be set to GL_TEXTURE_2D.
The level, format, and type parameters are similar to the ones used for glTexImage2D(). level is the mipmap level-of-detail number. It is not an error to specify a width or height of zero, but the subimage will have no effect. format and type describe the format and data type of the texture image data. The subimage is also affected by modes set by glPixelStore*() and glPixelTransfer*().
pixels contains the texture data for the subimage. width and height are the dimensions of the subregion that is replacing all or part of the current texture image. xoffset and yoffset specify the texel offset in the x and y directions (with (0, 0) at the lower-left corner of the texture) and specify where to put the subimage within the existing texture array. This region may not include any texels outside the range of the originally defined texture array.

In Example 9-3, some of the code from Example 9-1 has been modified so that pressing the `s' key drops a smaller checkered subimage into the existing image. (The resulting texture is shown in Figure 9-3.) Pressing the `r' key restores the original image. Example 9-3 shows the two routines, makeCheckImages() and keyboard(), that have been substantially changed. (See "Texture Objects" for more information about glBindTexture().)

Figure 9-3 : Texture with Subimage Added

Example 9-3 : Replacing a Texture Subimage: texsub.c

/*  Create checkerboard textures  */
#define checkImageWidth 64
#define checkImageHeight 64
#define subImageWidth 16
#define subImageHeight 16
static GLubyte checkImage[checkImageHeight][checkImageWidth][4];
static GLubyte subImage[subImageHeight][subImageWidth][4];

void makeCheckImages(void)
{
   int i, j, c;

   for (i = 0; i < checkImageHeight; i++) {
      for (j = 0; j < checkImageWidth; j++) {
         c = ((((i&0x8)==0)^((j&0x8))==0))*255;
         checkImage[i][j][0] = (GLubyte) c;
         checkImage[i][j][1] = (GLubyte) c;
         checkImage[i][j][2] = (GLubyte) c;
         checkImage[i][j][3] = (GLubyte) 255;
      }
   }
   for (i = 0; i < subImageHeight; i++) {
      for (j = 0; j < subImageWidth; j++) {
         c = ((((i&0x4)==0)^((j&0x4))==0))*255;
         subImage[i][j][0] = (GLubyte) c;
         subImage[i][j][1] = (GLubyte) 0;
         subImage[i][j][2] = (GLubyte) 0;
         subImage[i][j][3] = (GLubyte) 255;
      }
   }
}

void keyboard (unsigned char key, int x, int y)
{
   switch (key) {
      case `s':
      case `S':
         glBindTexture(GL_TEXTURE_2D, texName);
         glTexSubImage2D(GL_TEXTURE_2D, 0, 12, 44,
                         subImageWidth, subImageHeight, GL_RGBA,
                         GL_UNSIGNED_BYTE, subImage);
         glutPostRedisplay();
         break;
      case `r':
      case `R':
         glBindTexture(GL_TEXTURE_2D, texName);
         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
                      checkImageWidth, checkImageHeight, 0,
                      GL_RGBA, GL_UNSIGNED_BYTE, checkImage);
         glutPostRedisplay();
         break;
      case 27:
         exit(0);
         break;
      default:
         break;
   }
}

Once again, the framebuffer itself can be used as a source for texture data; this time, a texture subimage. glCopyTexSubImage2D() reads a rectangle of pixels from the framebuffer and replaces a portion of an existing texture array. (glCopyTexSubImage2D() is kind of a cross between glCopyTexImage2D() and glTexSubImage2D().)

void glCopyTexSubImage2D(GLenum target, GLint level,
GLint xoffset, GLint yoffset, GLint x, GLint y,
GLsizei width, GLsizei height);
Uses image data from the framebuffer to replace all or part of a contiguous subregion of the current, existing two-dimensional texture image. The pixels are read from the current GL_READ_BUFFER and are processed exactly as if glCopyPixels() had been called, stopping before final conversion. The settings of glPixelStore*() and glPixelTransfer*() are applied.
The target parameter must be set to GL_TEXTURE_2D. level is the mipmap level-of-detail number. xoffset and yoffset specify the texel offset in the x and y directions (with (0, 0) at the lower-left corner of the texture) and specify where to put the subimage within the existing texture array. The subimage texture array is taken from a screen-aligned pixel rectangle with the lower-left corner at coordinates specified by the (x, y) parameters. The width and height parameters specify the size of this subimage rectangle.

One-Dimensional Textures

Sometimes a one-dimensional texture is sufficient - for example, if you're drawing textured bands where all the variation is in one direction. A one-dimensional texture behaves like a two-dimensional one with height = 1, and without borders along the top and bottom. All the two-dimensional texture and subtexture definition routines have corresponding one-dimensional routines. To create a simple one-dimensional texture, use glTexImage1D().

void glTexImage1D(GLenum target, GLint level, GLint internalFormat,
GLsizei width, GLint border, GLenum format,
GLenum type, const GLvoid *pixels);
Defines a one-dimensional texture. All the parameters have the same meanings as for glTexImage2D(), except that the image is now a one-dimensional array of texels. As before, the value of width is 2m (or 2m+2, if there's a border), where m is a nonnegative integer. You can supply mipmaps, proxies (set target to GL_PROXY_TEXTURE_1D), and the same filtering options are available as well.

For a sample program that uses a one-dimensional texture map, see Example 9-6.

To replace all or some of the texels of a one-dimensional texture, use glTexSubImage1D().

void glTexSubImage1D(GLenum target, GLint level, GLint xoffset,
GLsizei width, GLenum format,
GLenum type, const GLvoid *pixels);
Defines a one-dimensional texture array that replaces all or part of a contiguous subregion (in 1D, a row) of the current, existing one-dimensional texture image. The target parameter must be set to GL_TEXTURE_1D.
The level, format, and type parameters are similar to the ones used for glTexImage1D(). level is the mipmap level-of-detail number. format and type describe the format and data type of the texture image data. The subimage is also affected by modes set by glPixelStore*() or glPixelTransfer*().

pixels contains the texture data for the subimage. width is the number of texels that replace part or all of the current texture image. xoffset specifies the texel offset for where to put the subimage within the existing texture array.

To use the framebuffer as the source of a new or replacement for an old one-dimensional texture, use either glCopyTexImage1D() or glCopyTexSubImage1D().

void glCopyTexImage1D(GLenum target, GLint level,
GLint internalFormat, GLint x, GLint y,
GLsizei width, GLint border);
Creates a one-dimensional texture, using framebuffer data to define the texels. The pixels are read from the current GL_READ_BUFFER and are processed exactly as if glCopyPixels() had been called but stopped before final conversion. The settings of glPixelStore*() and glPixelTransfer*() are applied.
The target parameter must be set to the constant GL_TEXTURE_1D. The level, internalFormat, and border parameters have the same effects that they have for glCopyTexImage2D(). The texture array is taken from a row of pixels with the lower-left corner at coordinates specified by the (x, y) parameters. The width parameter specifies the number of pixels in this row. The value of width is 2m (or 2m+2 if there's a border), where m is a nonnegative integer.
void glCopyTexSubImage1D(GLenum target, GLint level, GLint xoffset,
GLint x, GLint y, GLsizei width);
Uses image data from the framebuffer to replace all or part of a contiguous subregion of the current, existing one-dimensional texture image. The pixels are read from the current GL_READ_BUFFER and are processed exactly as if glCopyPixels() had been called but stopped before final conversion. The settings of glPixelStore*() and glPixelTransfer*() are applied.
The target parameter must be set to GL_TEXTURE_1D. level is the mipmap level-of-detail number. xoffset specifies the texel offset and specifies where to put the subimage within the existing texture array. The subimage texture array is taken from a row of pixels with the lower-left corner at coordinates specified by the (x, y) parameters. The width parameter specifies the number of pixels in this row.

Using a Texture's Borders

Advanced

If you need to apply a larger texture map than your implementation of OpenGL allows, you can, with a little care, effectively make larger textures by tiling with several different textures. For example, if you need a texture twice as large as the maximum allowed size mapped to a square, draw the square as four subsquares, and load a different texture before drawing each piece.

Since only a single texture map is available at one time, this approach might lead to problems at the edges of the textures, especially if some form of linear filtering is enabled. The texture value to be used for pixels at the edges must be averaged with something beyond the edge, which, ideally, should come from the adjacent texture map. If you define a border for each texture whose texel values are equal to the values of the texels on the edge of the adjacent texture map, then the correct behavior results when linear filtering takes place.

To do this correctly, notice that each map can have eight neighbors - one adjacent to each edge, and one touching each corner. The values of the texels in the corner of the border need to correspond with the texels in the texture maps that touch the corners. If your texture is an edge or corner of the whole tiling, you need to decide what values would be reasonable to put in the borders. The easiest reasonable thing to do is to copy the value of the adjacent texel in the texture map. Remember that the border values need to be supplied at the same time as the texture-image data, so you need to figure this out ahead of time.

A texture's border color is also used if the texture is applied in such a way that it only partially covers a primitive. (See "Repeating and Clamping Textures" for more information about this situation.)

Multiple Levels of Detail

Advanced

Textured objects can be viewed, like any other objects in a scene, at different distances from the viewpoint. In a dynamic scene, as a textured object moves farther from the viewpoint, the texture map must decrease in size along with the size of the projected image. To accomplish this, OpenGL has to filter the texture map down to an appropriate size for mapping onto the object, without introducing visually disturbing artifacts. For example, to render a brick wall, you may use a large (say 128 ´ 128 texel) texture image when it is close to the viewer. But if the wall is moved farther away from the viewer until it appears on the screen as a single pixel, then the filtered textures may appear to change abruptly at certain transition points.

To avoid such artifacts, you can specify a series of prefiltered texture maps of decreasing resolutions, called mipmaps, as shown in Figure 9-4. The term mipmap was coined by Lance Williams, when he introduced the idea in his paper, "Pyramidal Parametrics" (SIGGRAPH 1983 Proceedings). Mip stands for the Latin multim im parvo, meaning "many things in a small place." Mipmapping uses some clever methods to pack image data into memory.

Figure 9-4 : Mipmaps

When using mipmapping, OpenGL automatically determines which texture map to use based on the size (in pixels) of the object being mapped. With this approach, the level of detail in the texture map is appropriate for the image that's drawn on the screen - as the image of the object gets smaller, the size of the texture map decreases. Mipmapping requires some extra computation and texture storage area; however, when it's not used, textures that are mapped onto smaller objects might shimmer and flash as the objects move.

To use mipmapping, you must provide all sizes of your texture in powers of 2 between the largest size and a 1 ´ 1 map. For example, if your highest-resolution map is 64 ´ 16, you must also provide maps of size 32 ´ 8, 16 ´ 4, 8 ´ 2, 4 ´ 1, 2 ´ 1, and 1 ´ 1. The smaller maps are typically filtered and averaged-down versions of the largest map in which each texel in a smaller texture is an average of the corresponding four texels in the larger texture. (Since OpenGL doesn't require any particular method for calculating the smaller maps, the differently sized textures could be totally unrelated. In practice, unrelated textures would make the transitions between mipmaps extremely noticeable.)

To specify these textures, call glTexImage2D() once for each resolution of the texture map, with different values for the level, width, height, and image parameters. Starting with zero, level identifies which texture in the series is specified; with the previous example, the largest texture of size 64 ´ 16 would be declared with level = 0, the 32 ´ 8 texture with level = 1, and so on. In addition, for the mipmapped textures to take effect, you need to choose one of the appropriate filtering methods described in the next section.

Example 9-4 illustrates the use of a series of six texture maps decreasing in size from 32 ´ 32 to 1 ´ 1. This program draws a rectangle that extends from the foreground far back in the distance, eventually disappearing at a point, as shown in "Plate 20" in Appendix I. Note that the texture coordinates range from 0.0 to 8.0 so 64 copies of the texture map are required to tile the rectangle, eight in each direction. To illustrate how one texture map succeeds another, each map has a different color.

Example 9-4 : Mipmap Textures: mipmap.c

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <stdlib.h>

GLubyte mipmapImage32[32][32][4];
GLubyte mipmapImage16[16][16][4];
GLubyte mipmapImage8[8][8][4];
GLubyte mipmapImage4[4][4][4];
GLubyte mipmapImage2[2][2][4];
GLubyte mipmapImage1[1][1][4];

static GLuint texName;

void makeImages(void)
{
   int i, j;

   for (i = 0; i < 32; i++) {
      for (j = 0; j < 32; j++) {
         mipmapImage32[i][j][0] = 255;
         mipmapImage32[i][j][1] = 255;
         mipmapImage32[i][j][2] = 0;
         mipmapImage32[i][j][3] = 255;
      }
   }
   for (i = 0; i < 16; i++) {
      for (j = 0; j < 16; j++) {
         mipmapImage16[i][j][0] = 255;
         mipmapImage16[i][j][1] = 0;
         mipmapImage16[i][j][2] = 255;
         mipmapImage16[i][j][3] = 255;
      }
   }
   for (i = 0; i < 8; i++) {
      for (j = 0; j < 8; j++) {
         mipmapImage8[i][j][0] = 255;
         mipmapImage8[i][j][1] = 0;
         mipmapImage8[i][j][2] = 0;
         mipmapImage8[i][j][3] = 255;
      }
   }
   for (i = 0; i < 4; i++) {
      for (j = 0; j < 4; j++) {
         mipmapImage4[i][j][0] = 0;
         mipmapImage4[i][j][1] = 255;
         mipmapImage4[i][j][2] = 0;
         mipmapImage4[i][j][3] = 255;
      }
   }
   for (i = 0; i < 2; i++) {
      for (j = 0; j < 2; j++) {
         mipmapImage2[i][j][0] = 0;
         mipmapImage2[i][j][1] = 0;
         mipmapImage2[i][j][2] = 255;
         mipmapImage2[i][j][3] = 255;
      }
   }
   mipmapImage1[0][0][0] = 255;
   mipmapImage1[0][0][1] = 255;
   mipmapImage1[0][0][2] = 255;
   mipmapImage1[0][0][3] = 255;
}

void init(void)
{
   glEnable(GL_DEPTH_TEST);
   glShadeModel(GL_FLAT);

   glTranslatef(0.0, 0.0, -3.6);
   makeImages();
   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

   glGenTextures(1, &texName);
   glBindTexture(GL_TEXTURE_2D, texName);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
                   GL_NEAREST);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
                   GL_NEAREST_MIPMAP_NEAREST);
   glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 32, 32, 0,
                GL_RGBA, GL_UNSIGNED_BYTE, mipmapImage32);
   glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, 16, 16, 0,
                GL_RGBA, GL_UNSIGNED_BYTE, mipmapImage16);
   glTexImage2D(GL_TEXTURE_2D, 2, GL_RGBA, 8, 8, 0,
                GL_RGBA, GL_UNSIGNED_BYTE, mipmapImage8);
   glTexImage2D(GL_TEXTURE_2D, 3, GL_RGBA, 4, 4, 0,
                GL_RGBA, GL_UNSIGNED_BYTE, mipmapImage4);
   glTexImage2D(GL_TEXTURE_2D, 4, GL_RGBA, 2, 2, 0,
                GL_RGBA, GL_UNSIGNED_BYTE, mipmapImage2);
   glTexImage2D(GL_TEXTURE_2D, 5, GL_RGBA, 1, 1, 0,
                GL_RGBA, GL_UNSIGNED_BYTE, mipmapImage1);

   glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
   glEnable(GL_TEXTURE_2D);
}

void display(void)
{
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   glBindTexture(GL_TEXTURE_2D, texName);
   glBegin(GL_QUADS);
   glTexCoord2f(0.0, 0.0); glVertex3f(-2.0, -1.0, 0.0);
   glTexCoord2f(0.0, 8.0); glVertex3f(-2.0, 1.0, 0.0);
   glTexCoord2f(8.0, 8.0); glVertex3f(2000.0, 1.0, -6000.0);
   glTexCoord2f(8.0, 0.0); glVertex3f(2000.0, -1.0, -6000.0);
   glEnd();
   glFlush();
}

void reshape(int w, int h)
{
   glViewport(0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluPerspective(60.0, (GLfloat)w/(GLfloat)h, 1.0, 30000.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
}

void keyboard (unsigned char key, int x, int y)
{
   switch (key) {
      case 27:
         exit(0);
         break;
      default:
         break;
   }
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);
   glutInitWindowSize(500, 500);
   glutInitWindowPosition(50, 50);
   glutCreateWindow(argv[0]);
   init();
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyboard);
   glutMainLoop();
   return 0;
}

Example 9-4 illustrates mipmapping by making each mipmap a different color so that it's obvious when one map is replaced by another. In a real situation, you define mipmaps so that the transition is as smooth as possible. Thus, the maps of lower resolution are usually filtered versions of an original, high-resolution map. The construction of a series of such mipmaps is a software process, and thus isn't part of OpenGL, which is simply a rendering library. However, since mipmap construction is such an important operation, however, the OpenGL Utility Library contains two routines that aid in the manipulation of images to be used as mipmapped textures.

Assuming you have constructed the level 0, or highest-resolution map, the routines gluBuild1DMipmaps() and gluBuild2DMipmaps() construct and define the pyramid of mipmaps down to a resolution of 1 ´ 1 (or 1, for one-dimensional texture maps). If your original image has dimensions that are not exact powers of 2, gluBuild*DMipmaps() helpfully scales the image to the nearest power of 2.

int gluBuild1DMipmaps(GLenum target, GLint components, GLint width,
GLenum format, GLenum type, void *data);
int gluBuild2DMipmaps(GLenum target, GLint components, GLint width,
GLint height, GLenum format, GLenum type,
void *data);
Constructs a series of mipmaps and calls glTexImage*D() to load the images. The parameters for target, components, width, height, format, type, and data are exactly the same as those for glTexImage1D() and glTexImage2D(). A value of 0 is returned if all the mipmaps are constructed successfully; otherwise, a GLU error code is returned.

Filtering

Texture maps are square or rectangular, but after being mapped to a polygon or surface and transformed into screen coordinates, the individual texels of a texture rarely correspond to individual pixels of the final screen image. Depending on the transformations used and the texture mapping applied, a single pixel on the screen can correspond to anything from a tiny portion of a texel (magnification) to a large collection of texels (minification), as shown in Figure 9-5. In either case, it's unclear exactly which texel values should be used and how they should be averaged or interpolated. Consequently, OpenGL allows you to specify any of several filtering options to determine these calculations. The options provide different trade-offs between speed and image quality. Also, you can specify independently the filtering methods for magnification and minification.

Figure 9-5 : Texture Magnification and Minification

In some cases, it isn't obvious whether magnification or minification is called for. If the mipmap needs to be stretched (or shrunk) in both the x and y directions, then magnification (or minification) is needed. If the mipmap needs to be stretched in one direction and shrunk in the other, OpenGL makes a choice between magnification and minification that in most cases gives the best result possible. It's best to try to avoid these situations by using texture coordinates that map without such distortion. (See "Computing Appropriate Texture Coordinates.")

The following lines are examples of how to use glTexParameter*() to specify the magnification and minification filtering methods:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
                GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
                GL_NEAREST);

The first argument to glTexParameter*() is either GL_TEXTURE_2D or GL_TEXTURE_1D, depending on whether you're working with two- or one-dimensional textures. For the purposes of this discussion, the second argument is either GL_TEXTURE_MAG_FILTER or GL_TEXTURE_MIN_FILTER to indicate whether you're specifying the filtering method for magnification or minification. The third argument specifies the filtering method; Table 9-1 lists the possible values.

Table 9-1 : Filtering Methods for Magnification and Minification

Parameter | Values
GL_TEXTURE_MAG_FILTER | GL_NEAREST or GL_LINEAR
GL_TEXTURE_MIN_FILTER | GL_NEAREST, GL_LINEAR, GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_NEAREST, or GL_LINEAR_MIPMAP_LINEAR

If you choose GL_NEAREST, the texel with coordinates nearest the center of the pixel is used for both magnification and minification. This can result in aliasing artifacts (sometimes severe). If you choose GL_LINEAR, a weighted linear average of the 2 ´ 2 array of texels that lie nearest to the center of the pixel is used, again for both magnification and minification. When the texture coordinates are near the edge of the texture map, the nearest 2 ´ 2 array of texels might include some that are outside the texture map. In these cases, the texel values used depend on whether GL_REPEAT or GL_CLAMP is in effect and whether you've assigned a border for the texture. (See "Using a Texture's Borders.") GL_NEAREST requires less computation than GL_LINEAR and therefore might execute more quickly, but GL_LINEAR provides smoother results.

With magnification, even if you've supplied mipmaps, the largest texture map (level = 0) is always used. With minification, you can choose a filtering method that uses the most appropriate one or two mipmaps, as described in the next paragraph. (If GL_NEAREST or GL_LINEAR is specified with minification, the largest texture map is used.)

As shown in Table 9-1, four additional filtering choices are available when minifying with mipmaps. Within an individual mipmap, you can choose the nearest texel value with GL_NEAREST_MIPMAP_NEAREST, or you can interpolate linearly by specifying GL_LINEAR_MIPMAP_NEAREST. Using the nearest texels is faster but yields less desirable results. The particular mipmap chosen is a function of the amount of minification required, and there's a cutoff point from the use of one particular mipmap to the next. To avoid a sudden transition, use GL_NEAREST_MIPMAP_LINEAR or GL_LINEAR_MIPMAP_LINEAR to linearly interpolate texel values from the two nearest best choices of mipmaps. GL_NEAREST_MIPMAP_LINEAR selects the nearest texel in each of the two maps and then interpolates linearly between these two values. GL_LINEAR_MIPMAP_LINEAR uses linear interpolation to compute the value in each of two maps and then interpolates linearly between these two values. As you might expect, GL_LINEAR_MIPMAP_LINEAR generally produces the smoothest results, but it requires the most computation and therefore might be the slowest.

Texture Objects

Texture objects are an important new feature in release 1.1 of OpenGL. A texture object stores texture data and makes it readily available. You can now control many textures and go back to textures that have been previously loaded into your texture resources. Using texture objects is usually the fastest way to apply textures, resulting in big performance gains, because it is almost always much faster to bind (reuse) an existing texture object than it is to reload a texture image using glTexImage*D().

Also, some implementations support a limited working set of high-performance textures. You can use texture objects to load your most often used textures into this limited area.

To use texture objects for your texture data, take these steps.

Generate texture names.

Initially bind (create) texture objects to texture data, including the image arrays and texture properties.

If your implementation supports a working set of high-performance textures, see if you have enough space for all your texture objects. If there isn't enough space, you may wish to establish priorities for each texture object so that more often used textures stay in the working set.

Bind and rebind texture objects, making their data currently available for rendering textured models.

Naming A Texture Object

Any nonzero unsigned integer may be used as a texture name. To avoid accidentally reusing names, consistently use glGenTextures() to provide unused texture names.

void glGenTextures(GLsizei n, GLuint *textureNames);
Returns n currently unused names for texture objects in the array textureNames. The names returned in textureNames do not have to be a contiguous set of integers.
The names in textureNames are marked as used, but they acquire texture state and dimensionality (1D or 2D) only when they are first bound.
Zero is a reserved texture name and is never returned as a texture name by glGenTextures().

glIsTexture() determines if a texture name is actually in use. If a texture name was returned by glGenTextures() but has not yet been bound (calling glBindTexture() with the name at least once), then glIsTexture() returns GL_FALSE.

GLboolean glIsTexture(GLuint textureName);
Returns GL_TRUE if textureName is the name of a texture that has been bound and has not been subsequently deleted. Returns GL_FALSE if textureName is zero or textureName is a nonzero value that is not the name of an existing texture.

Creating and Using Texture Objects

The same routine, glBindTexture(), both creates and uses texture objects. When a texture name is initially bound (used with glBindTexture()), a new texture object is created with default values for the texture image and texture properties. Subsequent calls to glTexImage*(), glTexSubImage*(), glCopyTexImage*(), glCopyTexSubImage*(), glTexParameter*(), and glPrioritizeTextures() store data in the texture object. The texture object may contain a texture image and associated mipmap images (if any), including associated data such as width, height, border width, internal format, resolution of components, and texture properties. Saved texture properties include minification and magnification filters, wrapping modes, border color, and texture priority.

When a texture object is subsequently bound once again, its data becomes the current texture state. (The state of the previously bound texture is replaced.)

void glBindTexture(GLenum target, GLuint textureName);
glBindTexture() does three things. When using textureName of an unsigned integer other than zero for the first time, a new texture object is created and assigned that name. When binding to a previously created texture object, that texture object becomes active. When binding to a textureName value of zero, OpenGL stops using texture objects and returns to the unnamed default texture.
When a texture object is initially bound (that is, created), it assumes the dimensionality of target, which is either GL_TEXTURE_1D or GL_TEXTURE_2D. Immediately upon its initial binding, the state of texture object is equivalent to the state of the default GL_TEXTURE_1D or GL_TEXTURE_2D (depending upon its dimensionality) at the initialization of OpenGL. In this initial state, texture properties such as minification and magnification filters, wrapping modes, border color, and texture priority are set to their default values.

In Example 9-5, two texture objects are created in init(). In display(), each texture object is used to render a different four-sided polygon.

Example 9-5 : Binding Texture Objects: texbind.c

#define checkImageWidth 64
#define checkImageHeight 64
static GLubyte checkImage[checkImageHeight][checkImageWidth][4];
static GLubyte otherImage[checkImageHeight][checkImageWidth][4];

static GLuint texName[2];

void makeCheckImages(void)
{
   int i, j, c;

   for (i = 0; i < checkImageHeight; i++) {
      for (j = 0; j < checkImageWidth; j++) {
         c = ((((i&0x8)==0)^((j&0x8))==0))*255;
         checkImage[i][j][0] = (GLubyte) c;
         checkImage[i][j][1] = (GLubyte) c;
         checkImage[i][j][2] = (GLubyte) c;
         checkImage[i][j][3] = (GLubyte) 255;
         c = ((((i&0x10)==0)^((j&0x10))==0))*255;
         otherImage[i][j][0] = (GLubyte) c;
         otherImage[i][j][1] = (GLubyte) 0;
         otherImage[i][j][2] = (GLubyte) 0;
         otherImage[i][j][3] = (GLubyte) 255;
      }
   }
}

void init(void)
{
   glClearColor (0.0, 0.0, 0.0, 0.0);
   glShadeModel(GL_FLAT);
   glEnable(GL_DEPTH_TEST);

   makeCheckImages();
   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

   glGenTextures(2, texName);
   glBindTexture(GL_TEXTURE_2D, texName[0]);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
                   GL_NEAREST);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
                   GL_NEAREST);
   glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, checkImageWidth,
                checkImageHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                checkImage);

   glBindTexture(GL_TEXTURE_2D, texName[1]);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
                   GL_NEAREST);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
                   GL_NEAREST);
   glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
   glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, checkImageWidth,
                checkImageHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                otherImage);
   glEnable(GL_TEXTURE_2D);
}

void display(void)
{
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   glBindTexture(GL_TEXTURE_2D, texName[0]);
   glBegin(GL_QUADS);
   glTexCoord2f(0.0, 0.0); glVertex3f(-2.0, -1.0, 0.0);
   glTexCoord2f(0.0, 1.0); glVertex3f(-2.0, 1.0, 0.0);
   glTexCoord2f(1.0, 1.0); glVertex3f(0.0, 1.0, 0.0);
   glTexCoord2f(1.0, 0.0); glVertex3f(0.0, -1.0, 0.0);
   glEnd();
   glBindTexture(GL_TEXTURE_2D, texName[1]);
   glBegin(GL_QUADS);
   glTexCoord2f(0.0, 0.0); glVertex3f(1.0, -1.0, 0.0);
   glTexCoord2f(0.0, 1.0); glVertex3f(1.0, 1.0, 0.0);
   glTexCoord2f(1.0, 1.0); glVertex3f(2.41421, 1.0, -1.41421);
   glTexCoord2f(1.0, 0.0); glVertex3f(2.41421, -1.0, -1.41421);
   glEnd();
   glFlush();
}

Whenever a texture object is bound once again, you may edit the contents of the bound texture object. Any commands you call that change the texture image or other properties change the contents of the currently bound texture object as well as the current texture state.

In Example 9-5, after completion of display(), you are still bound to the texture named by the contents of texName[1]. Be careful that you don't call a spurious texture routine that changes the data in that texture object.

When using mipmaps, all related mipmaps of a single texture image must be put into a single texture object. In Example 9-4, levels 05 of a mipmapped texture image are put into a single texture object named texName.

Cleaning Up Texture Objects

As you bind and unbind texture objects, their data still sits around somewhere among your texture resources. If texture resources are limited, deleting textures may be one way to free up resources.

void glDeleteTextures(GLsizei n, const GLuint *textureNames);
Deletes n texture objects, named by elements in the array textureNames. The freed texture names may now be reused (for example, by glGenTextures()).
If a texture that is currently bound is deleted, the binding reverts to the default texture, as if glBindTexture() were called with zero for the value of textureName. Attempts to delete nonexistent texture names or the texture name of zero are ignored without generating an error.

A Working Set of Resident Textures

Some OpenGL implementations support a working set of high-performance textures, which are said to be resident. Typically, these implementations have specialized hardware to perform texture operations and a limited hardware cache to store texture images. In this case, using texture objects is recommended, because you are able to load many textures into the working set and then control them.

If all the textures required by the application exceed the size of the cache, some textures cannot be resident. If you want to find out if a single texture is currently resident, bind its object, and then use glGetTexParameter*v() to find out the value associated with the GL_TEXTURE_RESIDENT state. If you want to know about the texture residence status of many textures, use glAreTexturesResident().

GLboolean glAreTexturesResident(GLsizei n, const
GLuint*textureNames, GLboolean *residences);
Queries the texture residence status of the n texture objects, named in the array textureNames. residences is an array in which texture residence status is returned for the corresponding texture objects in the array textureNames. If all the named textures in textureNames are resident, the glAreTexturesResident() function returns GL_TRUE, and the contents of the array residences are undisturbed. If any texture in textureNames is not resident, then glAreTexturesResident() returns GL_FALSE and the elements in residences, which correspond to nonresident texture objects in textureNames, are also set to GL_FALSE.

Note that glAreTexturesResident() returns the current residence status. Texture resources are very dynamic, and texture residence status may change at any time. Some implementations cache textures when they are first used. It may be necessary to draw with the texture before checking residency.

If your OpenGL implementation does not establish a working set of high-performance textures, then the texture objects are always considered resident. In that case, glAreTexturesResident() always returns GL_TRUE and basically provides no information.

Texture Residence Strategies

If you can create a working set of textures and want to get the best texture performance possible, you really have to know the specifics of your implementation and application. For example, with a visual simulation or video game, you have to maintain performance in all situations. In that case, you should never access a nonresident texture. For these applications, you want to load up all your textures upon initialization and make them all resident. If you don't have enough texture memory available, you may need to reduce the size, resolution, and levels of mipmaps for your texture images, or you may use glTexSubImage*() to repeatedly reuse the same texture memory.

For applications that create textures "on the fly," nonresident textures may be unavoidable. If some textures are used more frequently than others, you may assign a higher priority to those texture objects to increase their likelihood of being resident. Deleting texture objects also frees up space. Short of that, assigning a lower priority to a texture object may make it first in line for being moved out of the working set, as resources dwindle. glPrioritizeTextures() is used to assign priorities to texture objects.

void glPrioritizeTextures(GLsizei n, const GLuint *textureNames,
const GLclampf *priorities);
Assigns the n texture objects, named in the array textureNames, the texture residence priorities in the corresponding elements of the array priorities. The priority values in the array priorities are clamped to the range [0.0, 1.0] before being assigned. Zero indicates the lowest priority; these textures are least likely to be resident. One indicates the highest priority.
glPrioritizeTextures() does not require that any of the textures in textureNames be bound. However, the priority might not have any effect on a texture object until it is initially bound.

glTexParameter*() also may be used to set a single texture's priority, but only if the texture is currently bound. In fact, use of glTexParameter*() is the only way to set the priority of a default texture.

If texture objects have equal priority, typical implementations of OpenGL apply a least recently used (LRU) strategy to decide which texture objects to move out of the working set. If you know that your OpenGL implementation has this behavior, then having equal priorities for all texture objects creates a reasonable LRU system for reallocating texture resources.

If your implementation of OpenGL doesn't use an LRU strategy for texture objects of equal priority (or if you don't know how it decides), you can implement your own LRU strategy by carefully maintaining the texture object priorities. When a texture is used (bound), you can maximize its priority, which reflects its recent use. Then, at regular (time) intervals, you can degrade the priorities of all texture objects.

Note: Fragmentation of texture memory can be a problem, especially if you're deleting and creating lots of new textures. Although it is even possible that you can load all the texture objects into a working set by binding them in one sequence, binding them in a different sequence may leave some textures nonresident.

Texture Functions

In all the examples so far in this chapter, the values in the texture map have been used directly as colors to be painted on the surface being rendered. You can also use the values in the texture map to modulate the color that the surface would be rendered without texturing, or to blend the color in the texture map with the original color of the surface. You choose one of four texturing functions by supplying the appropriate arguments to glTexEnv*().

void glTexEnv{if}(GLenum target, GLenum pname, TYPEparam);
void glTexEnv{if}v(GLenum target, GLenum pname, TYPE *param);
Sets the current texturing function. target must be GL_TEXTURE_ENV. If pname is GL_TEXTURE_ENV_MODE, param can be GL_DECAL, GL_REPLACE, GL_MODULATE, or GL_BLEND, to specify how texture values are to be combined with the color values of the fragment being processed. If pname is GL_TEXTURE_ENV_COLOR, param is an array of four floating-point values representing R, G, B, and A components. These values are used only if the GL_BLEND texture function has been specified as well.

The combination of the texturing function and the base internal format determine how the textures are applied for each component of the texture. The texturing function operates on selected components of the texture and the color values that would be used with no texturing. (Note that the selection is performed after the pixel-transfer function has been applied.) Recall that when you specify your texture map with glTexImage*D(), the third argument is the internal format to be selected for each texel.

Table 9-2 and Table 9-3 show how the texturing function and base internal format determine the texturing application formula used for each component of the texture. There are six base internal formats (the letters in parentheses represent their values in the tables): GL_ALPHA (A), GL_LUMINANCE (L), GL_LUMINANCE_ALPHA (L and A), GL_INTENSITY (I), GL_RGB (C), and GL_RGBA (C and A). Other internal formats specify desired resolutions of the texture components and can be matched to one of these six base internal formats.

Table 9-2 : Replace and Modulate Texture Function

Base Internal Format | Replace Texture Function | Modulate Texture Function
GL_ALPHA | C = Cf, A = At | C = Cf, A = AfAt
GL_LUMINANCE | C = Lt, A = Af | C = CfLt, A = Af
GL_LUMINANCE_ALPHA | C = Lt, A = At | C = CfLt, A = AfAt
GL_INTENSITY | C = It, A = It | C = CfIt, A = AfIt
GL_RGB | C = Ct, A = Af | C = CfCt, A = Af
GL_RGBA | C = Ct, A = At | C = CfCt, A = AfAt

Table 9-3 : Decal and Blend Texture Function

Base Internal Format | Decal Texture Function | Blend Texture Function
GL_ALPHA | undefined | C = Cf, A = AfAt
GL_LUMINANCE | undefined | C = Cf(1-Lt) + CcLt, A = Af
GL_LUMINANCE_ALPHA | undefined | C = Cf(1-Lt) + CcLt, A = AfAt
GL_INTENSITY | undefined | C = Cf(1-It) + CcIt, A = Af(1-It) + AcIt,
GL_RGB | C = Ct, A = Af | C = Cf(1-Ct) + CcCt, A = Af
GL_RGBA | C = Cf(1-At) + CtAt, A = Af | C = Cf(1-Ct) + CcCt, A = AfAt

Note: In Table 9-2 and Table 9-3, a subscript of t indicates a texture value, f indicates the incoming fragment value, c indicates the values assigned with GL_TEXTURE_ENV_COLOR, and no subscript indicates the final, computed value. Also in the tables, multiplication of a color triple by a scalar means multiplying each of the R, G, and B components by the scalar; multiplying (or adding) two color triples means multiplying (or adding) each component of the second by the corresponding component of the first.

The decal texture function makes sense only for the RGB and RGBA internal formats (remember that texture mapping doesn't work in color-index mode). With the RGB internal format, the color that would have been painted in the absence of any texture mapping (the fragment's color) is replaced by the texture color, and its alpha is unchanged. With the RGBA internal format, the fragment's color is blended with the texture color in a ratio determined by the texture alpha, and the fragment's alpha is unchanged. You use the decal texture function in situations where you want to apply an opaque texture to an object - if you were drawing a soup can with an opaque label, for example. The decal texture function also can be used to apply an alpha blended texture, such as an insignia onto an airplane wing.

The replacement texture function is similar to decal; in fact, for the RGB internal format, they are exactly the same. With all the internal formats, the component values are either replaced or left alone.

For modulation, the fragment's color is modulated by the contents of the texture map. If the base internal format is GL_LUMINANCE, GL_LUMINANCE_ALPHA, or GL_INTENSITY, the color values are multiplied by the same value, so the texture map modulates between the fragment's color (if the luminance or intensity is 1) to black (if it's 0). For the GL_RGB and GL_RGBA internal formats, each of the incoming color components is multiplied by a corresponding (possibly different) value in the texture. If there's an alpha value, it's multiplied by the fragment's alpha. Modulation is a good texture function for use with lighting, since the lit polygon color can be used to attenuate the texture color. Most of the texture-mapping examples in the color plates use modulation for this reason. White, specular polygons are often used to render lit, textured objects, and the texture image provides the diffuse color.

The blending texture function is the only function that uses the color specified by GL_TEXTURE_ENV_COLOR. The luminance, intensity, or color value is used somewhat like an alpha value to blend the fragment's color with the GL_TEXTURE_ENV_COLOR. (See "Sample Uses of Blending" in Chapter 6 for the billboarding example, which uses a blended texture.)

Assigning Texture Coordinates

As you draw your texture-mapped scene, you must provide both object coordinates and texture coordinates for each vertex. After transformation, the object coordinates determine where on the screen that particular vertex is rendered. The texture coordinates determine which texel in the texture map is assigned to that vertex. In exactly the same way that colors are interpolated between two vertices of shaded polygons and lines, texture coordinates are also interpolated between vertices. (Remember that textures are rectangular arrays of data.)

Texture coordinates can comprise one, two, three, or four coordinates. They're usually referred to as the s, t, r, and q coordinates to distinguish them from object coordinates (x, y, z, and w) and from evaluator coordinates (u and v; see Chapter 12). For one-dimensional textures, you use the s coordinate; for two-dimensional textures, you use s and t. In Release 1.1, the r coordinate is ignored. (Some implementations have 3D texture mapping as an extension, and that extension uses the r coordinate.) The q coordinate, like w, is typically given the value 1 and can be used to create homogeneous coordinates; it's described as an advanced feature in "The q Coordinate." The command to specify texture coordinates, glTexCoord*(), is similar to glVertex*(), glColor*(), and glNormal*() - it comes in similar variations and is used the same way between glBegin() and glEnd() pairs. Usually, texture-coordinate values range from 0 to 1; values can be assigned outside this range, however, with the results described in "Repeating and Clamping Textures."

void glTexCoord{1234}{sifd}(TYPEcoords);
void glTexCoord{1234}{sifd}v(TYPE *coords);
Sets the current texture coordinates (s, t, r, q). Subsequent calls to glVertex*() result in those vertices being assigned the current texture coordinates. With glTexCoord1*(), the s coordinate is set to the specified value, t and r are set to 0, and q is set to 1. Using glTexCoord2*() allows you to specify s and t; r and q are set to 0 and 1, respectively. With glTexCoord3*(), q is set to 1 and the other coordinates are set as specified. You can specify all coordinates with glTexCoord4*(). Use the appropriate suffix (s, i, f, or d) and the corresponding value for TYPE (GLshort, GLint, GLfloat, or GLdouble) to specify the coordinates' data type. You can supply the coordinates individually, or you can use the vector version of the command to supply them in a single array. Texture coordinates are multiplied by the 4 ´ 4 texture matrix before any texture mapping occurs. (See "The Texture Matrix Stack." ) Note that integer texture coordinates are interpreted directly rather than being mapped to the range [-1,1] as normal coordinates are.

The next section discusses how to calculate appropriate texture coordinates. Instead of explicitly assigning them yourself, you can choose to have texture coordinates calculated automatically by OpenGL as a function of the vertex coordinates. (See "Automatic Texture-Coordinate Generation.")

Computing Appropriate Texture Coordinates

Two-dimensional textures are square or rectangular images that are typically mapped to the polygons that make up a polygonal model. In the simplest case, you're mapping a rectangular texture onto a model that's also rectangular - for example, your texture is a scanned image of a brick wall, and your rectangle is to represent a brick wall of a building. Suppose the brick wall is square and the texture is square, and you want to map the whole texture to the whole wall. The texture coordinates of the texture square are (0, 0), (1, 0), (1, 1), and (0, 1) in counterclockwise order. When you're drawing the wall, just give those four coordinate sets as the texture coordinates as you specify the wall's vertices in counterclockwise order.

Now suppose that the wall is two-thirds as high as it is wide, and that the texture is again square. To avoid distorting the texture, you need to map the wall to a portion of the texture map so that the aspect ratio of the texture is preserved. Suppose that you decide to use the lower two-thirds of the texture map to texture the wall. In this case, use texture coordinates of (0,0), (1,0), (1,2/3), and (0,2/3) for the texture coordinates as the wall vertices are traversed in a counterclockwise order.

As a slightly more complicated example, suppose you'd like to display a tin can with a label wrapped around it on the screen. To obtain the texture, you purchase a can, remove the label, and scan it in. Suppose the label is 4 units tall and 12 units around, which yields an aspect ratio of 3 to 1. Since textures must have aspect ratios of 2n to 1, you can either simply not use the top third of the texture, or you can cut and paste the texture until it has the necessary aspect ratio. Suppose you decide not to use the top third. Now suppose the tin can is a cylinder approximated by thirty polygons of length 4 units (the height of the can) and width 12/30 (1/30 of the circumference of the can). You can use the following texture coordinates for each of the thirty approximating rectangles:

1: (0, 0), (1/30, 0), (1/30, 2/3), (0, 2/3)

2: (1/30, 0), (2/30, 0), (2/30, 2/3), (1/30, 2/3)

3: (2/30, 0), (3/30, 0), (3/30, 2/3), (2/30, 2/3)

. . .

30: (29/30, 0), (1, 0), (1, 2/3), (29/30, 2/3)

Only a few curved surfaces such as cones and cylinders can be mapped to a flat surface without geodesic distortion. Any other shape requires some distortion. In general, the higher the curvature of the surface, the more distortion of the texture is required.

If you don't care about texture distortion, it's often quite easy to find a reasonable mapping. For example, consider a sphere whose surface coordinates are given by (cos &thgr; cos &phgr; , cos &thgr; sin &phgr; , sin &thgr; ), where 0 £ &thgr; £ 2 &pgr; , and 0 £ &phgr; £ &pgr; . The &thgr; - &phgr; rectangle can be mapped directly to a rectangular texture map, but the closer you get to the poles, the more distorted the texture is. The entire top edge of the texture map is mapped to the north pole, and the entire bottom edge to the south pole. For other surfaces, such as that of a torus (doughnut) with a large hole, the natural surface coordinates map to the texture coordinates in a way that produces only a little distortion, so it might be suitable for many applications. Figure 9-6 shows two tori, one with a small hole (and therefore a lot of distortion near the center) and one with a large hole (and only a little distortion).

Figure 9-6 : Texture-Map Distortion

If you're texturing spline surfaces generated with evaluators (see Chapter 12), the u and v parameters for the surface can sometimes be used as texture coordinates. In general, however, there's a large artistic component to successfully mapping textures to polygonal approximations of curved surfaces.

Repeating and Clamping Textures

You can assign texture coordinates outside the range [0,1] and have them either clamp or repeat in the texture map. With repeating textures, if you have a large plane with texture coordinates running from 0.0 to 10.0 in both directions, for example, you'll get 100 copies of the texture tiled together on the screen. During repeating, the integer part of texture coordinates is ignored, and copies of the texture map tile the surface. For most applications where the texture is to be repeated, the texels at the top of the texture should match those at the bottom, and similarly for the left and right edges.

The other possibility is to clamp the texture coordinates: Any values greater than 1.0 are set to 1.0, and any values less than 0.0 are set to 0.0. Clamping is useful for applications where you want a single copy of the texture to appear on a large surface. If the surface-texture coordinates range from 0.0 to 10.0 in both directions, one copy of the texture appears in the lower corner of the surface. If you've chosen GL_LINEAR as the filtering method (see "Filtering"), an equally weighted combination of the border color and the texture color is used, as follows.

  • When repeating, the 2 ´ 2 array wraps to the opposite edge of the texture. Thus, texels on the right edge are averaged with those on the left, and top and bottom texels are also averaged.
  • If there is a border, then the texel from the border is used in the weighting. Otherwise, GL_TEXTURE_BORDER_COLOR is used. (If you've chosen GL_NEAREST as the filtering method, the border color is completely ignored.)

Note that if you are using clamping, you can avoid having the rest of the surface affected by the texture. To do this, use alpha values of 0 for the edges (or borders, if they are specified) of the texture. The decal texture function directly uses the texture's alpha value in its calculations. If you are using one of the other texture functions, you may also need to enable blending with good source and destination factors. (See "Blending" in Chapter 6.)

To see the effects of wrapping, you must have texture coordinates that venture beyond [0.0, 1.0]. Start with Example 9-1, and modify the texture coordinates for the squares by mapping the texture coordinates from 0.0 to 3.0 as follows:

glBegin(GL_QUADS);
  glTexCoord2f(0.0, 0.0); glVertex3f(-2.0, -1.0, 0.0);
  glTexCoord2f(0.0, 3.0); glVertex3f(-2.0, 1.0, 0.0);
  glTexCoord2f(3.0, 3.0); glVertex3f(0.0, 1.0, 0.0);
  glTexCoord2f(3.0, 0.0); glVertex3f(0.0, -1.0, 0.0);

  glTexCoord2f(0.0, 0.0); glVertex3f(1.0, -1.0, 0.0);
  glTexCoord2f(0.0, 3.0); glVertex3f(1.0, 1.0, 0.0);
  glTexCoord2f(3.0, 3.0); glVertex3f(2.41421, 1.0, -1.41421);
  glTexCoord2f(3.0, 0.0); glVertex3f(2.41421, -1.0, -1.41421); glEnd();

With GL_REPEAT wrapping, the result is as shown in Figure 9-7.

Figure 9-7 : Repeating a Texture

In this case, the texture is repeated in both the s and t directions, since the following calls are made to glTexParameter*():

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

If GL_CLAMP is used instead of GL_REPEAT for each direction, you see something similar to Figure 9-8.

Figure 9-8 : Clamping a Texture

You can also clamp in one direction and repeat in the other, as shown in Figure 9-9.

Figure 9-9 : Repeating and Clamping a Texture

You've now seen all the possible arguments for glTexParameter*(), which is summarized here.

void glTexParameter{if}(GLenum target, GLenum pname, TYPE param);
void glTexParameter{if}v(GLenum target, GLenum pname,
TYPE *param);
Sets various parameters that control how a texture is treated as it's applied to a fragment or stored in a texture object. The target parameter is either GL_TEXTURE_2D or GL_TEXTURE_1D to indicate a two- or one-dimensional texture. The possible values for pname and param are shown in Table 9-4. You can use the vector version of the command to supply an array of values for GL_TEXTURE_BORDER_COLOR, or you can supply individual values for other parameters using the nonvector version. If these values are supplied as integers, they're converted to floating-point according to Table 4-1; they're also clamped to the range [0,1].
Table 9-4 : glTexParameter*() Parameters
Parameter | Values
GL_TEXTURE_WRAP_S | GL_CLAMP, GL_REPEAT
GL_TEXTURE_WRAP_T | GL_CLAMP, GL_REPEAT
GL_TEXTURE_MAG_FILTER | GL_NEAREST, GL_LINEAR
GL_TEXTURE_MIN_FILTER | GL_NEAREST, GL_LINEAR, GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_LINEAR
GL_TEXTURE_BORDER_COLOR | any four values in [0.0, 1.0]
GL_TEXTURE_PRIORITY | [0.0, 1.0] for the current texture object

Try This

Figure 9-8 and Figure 9-9 are drawn using GL_NEAREST for the minification and magnification filter. What happens if you change the filter values to GL_LINEAR? Why?

Automatic Texture-Coordinate Generation

You can use texture mapping to make contours on your models or to simulate the reflections from an arbitrary environment on a shiny model. To achieve these effects, let OpenGL automatically generate the texture coordinates for you, rather than explicitly assigning them with glTexCoord*(). To generate texture coordinates automatically, use the command glTexGen().

void glTexGen{ifd}(GLenum coord, GLenum pname, TYPEparam);
void glTexGen{ifd}v(GLenum coord, GLenum pname, TYPE *param);
Specifies the functions for automatically generating texture coordinates. The first parameter, coord, must be GL_S, GL_T, GL_R, or GL_Q to indicate whether texture coordinate s, t, r, or q is to be generated. The pname parameter is GL_TEXTURE_GEN_MODE, GL_OBJECT_PLANE, or GL_EYE_PLANE. If it's GL_TEXTURE_GEN_MODE, param is an integer (or, in the vector version of the command, points to an integer) that's either GL_OBJECT_LINEAR, GL_EYE_LINEAR, or GL_SPHERE_MAP. These symbolic constants determine which function is used to generate the texture coordinate. With either of the other possible values for pname, param is a pointer to an array of values (for the vector version) specifying parameters for the texture-generation function.

The different methods of texture-coordinate generation have different uses. Specifying the reference plane in object coordinates is best for when a texture image remains fixed to a moving object. Thus, GL_OBJECT_LINEAR would be used for putting a wood grain on a table top. Specifying the reference plane in eye coordinates (GL_EYE_LINEAR) is best for producing dynamic contour lines on moving objects. GL_EYE_LINEAR may be used by specialists in geosciences, who are drilling for oil or gas. As the drill goes deeper into the ground, the drill may be rendered with different colors to represent the layers of rock at increasing depths. GL_SPHERE_MAP is predominantly used for environment mapping. (See "Environment Mapping.")

Creating Contours

When GL_TEXTURE_GEN_MODE and GL_OBJECT_LINEAR are specified, the generation function is a linear combination of the object coordinates of the vertex (xo,yo,zo,wo):

generated coordinate = p1x0 + p2y0 + p3z0 + p4w0

The p1, ..., p4 values are supplied as the param argument to glTexGen*v(), with pname set to GL_OBJECT_PLANE. With p1, ..., p4 correctly normalized, this function gives the distance from the vertex to a plane. For example, if p2 = p3 = p4 = 0 and p1 = 1, the function gives the distance between the vertex and the plane x = 0. The distance is positive on one side of the plane, negative on the other, and zero if the vertex lies on the plane.

Initially in Example 9-6, equally spaced contour lines are drawn on a teapot; the lines indicate the distance from the plane x = 0. The coefficients for the plane x = 0 are in this array:

static GLfloat xequalzero[] = {1.0, 0.0, 0.0, 0.0};

Since only one property is being shown (the distance from the plane), a one-dimensional texture map suffices. The texture map is a constant green color, except that at equally spaced intervals it includes a red mark. Since the teapot is sitting on the x-y plane, the contours are all perpendicular to its base. "Plate 18" in Appendix I shows the picture drawn by the program.

In the same example, pressing the `s' key changes the parameters of the reference plane to

static GLfloat slanted[] = {1.0, 1.0, 1.0, 0.0};

the contour stripes are parallel to the plane x + y + z = 0, slicing across the teapot at an angle, as shown in "Plate 18" in Appendix I. To restore the reference plane to its initial value, x = 0, press the `x' key.

Example 9-6 : Automatic Texture-Coordinate Generation: texgen.c

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <stdlib.h>
#include <stdio.h>

#define stripeImageWidth 32
GLubyte stripeImage[4*stripeImageWidth];

static GLuint texName;

void makeStripeImage(void)
{
   int j;

   for (j = 0; j < stripeImageWidth; j++) {
      stripeImage[4*j] = (GLubyte) ((j<=4) ? 255 : 0);
      stripeImage[4*j+1] = (GLubyte) ((j>4) ? 255 : 0);
      stripeImage[4*j+2] = (GLubyte) 0;
      stripeImage[4*j+3] = (GLubyte) 255;
   }
}

/*  planes for texture coordinate generation  */
static GLfloat xequalzero[] = {1.0, 0.0, 0.0, 0.0};
static GLfloat slanted[] = {1.0, 1.0, 1.0, 0.0};
static GLfloat *currentCoeff;
static GLenum currentPlane;
static GLint currentGenMode;

void init(void)
{
   glClearColor (0.0, 0.0, 0.0, 0.0);
   glEnable(GL_DEPTH_TEST);
   glShadeModel(GL_SMOOTH);

   makeStripeImage();
   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

   glGenTextures(1, &texName);
   glBindTexture(GL_TEXTURE_1D, texName);
   glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_REPEAT);
   glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER,
                   GL_LINEAR);
   glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER,
                   GL_LINEAR);
   glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, stripeImageWidth, 0,
                GL_RGBA, GL_UNSIGNED_BYTE, stripeImage);

   glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
   currentCoeff = xequalzero;
   currentGenMode = GL_OBJECT_LINEAR;
   currentPlane = GL_OBJECT_PLANE;
   glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, currentGenMode);
   glTexGenfv(GL_S, currentPlane, currentCoeff);

   glEnable(GL_TEXTURE_GEN_S);
   glEnable(GL_TEXTURE_1D);
   glEnable(GL_CULL_FACE);
   glEnable(GL_LIGHTING);
   glEnable(GL_LIGHT0);
   glEnable(GL_AUTO_NORMAL);
   glEnable(GL_NORMALIZE);
   glFrontFace(GL_CW);
   glCullFace(GL_BACK);
   glMaterialf (GL_FRONT, GL_SHININESS, 64.0);
}

void display(void)
{
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

   glPushMatrix ();
   glRotatef(45.0, 0.0, 0.0, 1.0);
   glBindTexture(GL_TEXTURE_1D, texName);
   glutSolidTeapot(2.0);
   glPopMatrix ();
   glFlush();
}

void reshape(int w, int h)
{
   glViewport(0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   if (w <= h)
      glOrtho (-3.5, 3.5, -3.5*(GLfloat)h/(GLfloat)w,
               3.5*(GLfloat)h/(GLfloat)w, -3.5, 3.5);
   else
      glOrtho (-3.5*(GLfloat)w/(GLfloat)h,
               3.5*(GLfloat)w/(GLfloat)h, -3.5, 3.5, -3.5, 3.5);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
}

void keyboard (unsigned char key, int x, int y)
{
   switch (key) {
      case `e':
      case `E':
         currentGenMode = GL_EYE_LINEAR;
         currentPlane = GL_EYE_PLANE;
         glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, currentGenMode);
         glTexGenfv(GL_S, currentPlane, currentCoeff);
         glutPostRedisplay();
         break;
      case `o':
      case `O':
         currentGenMode = GL_OBJECT_LINEAR;
         currentPlane = GL_OBJECT_PLANE;
         glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, currentGenMode);
         glTexGenfv(GL_S, currentPlane, currentCoeff);
         glutPostRedisplay();
         break;
      case `s':
      case `S':
         currentCoeff = slanted;
         glTexGenfv(GL_S, currentPlane, currentCoeff);
         glutPostRedisplay();
         break;
      case `x':
      case `X':
         currentCoeff = xequalzero;
         glTexGenfv(GL_S, currentPlane, currentCoeff);
         glutPostRedisplay();
         break;
      case 27:
         exit(0);
         break;
      default:
         break;
   }
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);
   glutInitWindowSize(256, 256);
   glutInitWindowPosition(100, 100);
   glutCreateWindow (argv[0]);
   init ();
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyboard);
   glutMainLoop();
   return 0;
}

You enable texture-coordinate generation for the s coordinate by passing GL_TEXTURE_GEN_S to glEnable(). To generate other coordinates, enable them with GL_TEXTURE_GEN_T, GL_TEXTURE_GEN_R, or GL_TEXTURE_GEN_Q. Use glDisable() with the appropriate constant to disable coordinate generation. Also note the use of GL_REPEAT to cause the contour lines to be repeated across the teapot.

The GL_OBJECT_LINEAR function calculates the texture coordinates in the model's coordinate system. Initially in Example 9-6, the GL_OBJECT_LINEAR function is used, so the contour lines remain perpendicular to the base of the teapot, no matter how the teapot is rotated or viewed. However, if you press the `e' key, the texture generation mode is changed from GL_OBJECT_LINEAR to GL_EYE_LINEAR, and the contour lines are calculated relative to the eye coordinate system. (Pressing the `o' key restores GL_OBJECT_LINEAR as the texture generation mode.) If the reference plane is x = 0, the result is a teapot with red stripes parallel to the y-z plane from the eye's point of view, as shown in "Plate 18" in Appendix I. Mathematically, you are multiplying the vector (p1p2p3p4) by the inverse of the modelview matrix to obtain the values used to calculate the distance to the plane. The texture coordinate is generated with the following function:

generated coordinate = p1'xe + p2'ye + p3'ze + p4'we

where (p1' p2' p3' p4') = (p1p2p3p4)M-1

In this case, (xe, ye, ze, we) are the eye coordinates of the vertex, and p1, ..., p4 are supplied as the param argument to glTexGen*() with pname set to GL_EYE_PLANE. The primed values are calculated only at the time they're specified so this operation isn't as computationally expensive as it looks.

In all these examples, a single texture coordinate is used to generate contours. The s and t texture coordinates can be generated independently, however, to indicate the distances to two different planes. With a properly constructed two-dimensional texture map, the resulting two sets of contours can be viewed simultaneously. For an added level of complexity, you can calculate the s coordinate using GL_OBJECT_LINEAR and the t coordinate using GL_EYE_LINEAR.

Environment Mapping

The goal of environment mapping is to render an object as if it were perfectly reflective, so that the colors on its surface are those reflected to the eye from its surroundings. In other words, if you look at a perfectly polished, perfectly reflective silver object in a room, you see the walls, floor, and other objects in the room reflected off the object. (A classic example of using environment mapping is the evil, morphing cyborg in the film Terminator 2.) The objects whose reflections you see depend on the position of your eye and on the position and surface angles of the silver object. To perform environment mapping, all you have to do is create an appropriate texture map and then have OpenGL generate the texture coordinates for you.

Environment mapping is an approximation based on the assumption that the items in the environment are far away compared to the surfaces of the shiny object - that is, it's a small object in a large room. With this assumption, to find the color of a point on the surface, take the ray from the eye to the surface, and reflect the ray off the surface. The direction of the reflected ray completely determines the color to be painted there. Encoding a color for each direction on a flat texture map is equivalent to putting a polished perfect sphere in the middle of the environment and taking a picture of it with a camera that has a lens with a very long focal length placed far away. Mathematically, the lens has an infinite focal length and the camera is infinitely far away. The encoding therefore covers a circular region of the texture map, tangent to the top, bottom, left, and right edges of the map. The texture values outside the circle make no difference, as they are never accessed in environment mapping.

To make a perfectly correct environment texture map, you need to obtain a large silvered sphere, take a photograph of it in some environment with a camera located an infinite distance away and with a lens that has an infinite focal length, and scan in the photograph. To approximate this result, you can use a scanned-in photograph of an environment taken with an extremely wide-angle (or fish-eye) lens. Plate 21 shows a photograph taken with such a lens and the results when that image is used as an environment map.

Once you've created a texture designed for environment mapping, you need to invoke OpenGL's environment-mapping algorithm. This algorithm finds the point on the surface of the sphere with the same tangent surface as the point on the object being rendered, and it paints the object's point with the color visible on the sphere at the corresponding point.

To automatically generate the texture coordinates to support environment mapping, use this code in your program:

glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);

The GL_SPHERE_MAP constant creates the proper texture coordinates for the environment mapping. As shown, you need to specify it for both the s and t directions. However, you don't have to specify any parameters for the texture-coordinate generation function.

The GL_SPHERE_MAP texture function generates texture coordinates using the following mathematical steps.

u is the unit vector pointing from the origin to the vertex (in eye coordinates).

n' is the current normal vector, after transformation to eye coordinates.

r is the reflection vector, (rxryrz)T, which is calculated by u 2n'n'Tu.

Then an interim value, m, is calculated by

Finally, the s and t texture coordinates are calculated by

and

Advanced Features

Advanced

This section describes how to manipulate the texture matrix stack and how to use the q coordinate. Both techniques are considered advanced, since you don't need them for many applications of texture mapping.

The Texture Matrix Stack

Just as your model coordinates are transformed by a matrix before being rendered, texture coordinates are multiplied by a 4 ´ 4 matrix before any texture mapping occurs. By default, the texture matrix is the identity, so the texture coordinates you explicitly assign or those that are automatically generated remain unchanged. By modifying the texture matrix while redrawing an object, however, you can make the texture slide over the surface, rotate around it, stretch and shrink, or any combination of the three. In fact, since the texture matrix is a completely general 4 ´ 4 matrix, effects such as perspective can be achieved.

When the four texture coordinates (s, t, r, q) are multiplied by the texture matrix, the resulting vector (s' t' r' q') is interpreted as homogeneous texture coordinates. In other words, the texture map is indexed by s'/q' and t'/q' . (Remember that r'/q' is ignored in standard OpenGL, but may be used by implementations that support a 3D texture extension.) The texture matrix is actually the top matrix on a stack, which must have a stack depth of at least two matrices. All the standard matrix-manipulation commands such as glPushMatrix(), glPopMatrix(), glMultMatrix(), and glRotate*() can be applied to the texture matrix. To modify the current texture matrix, you need to set the matrix mode to GL_TEXTURE, as follows:

glMatrixMode(GL_TEXTURE); /* enter texture matrix mode */
glRotated(...);
/* ... other matrix manipulations ... */
glMatrixMode(GL_MODELVIEW); /* back to modelview mode */

The q Coordinate

The mathematics of the q coordinate in a general four-dimensional texture coordinate is as described in the previous section. You can make use of q in cases where more than one projection or perspective transformation is needed. For example, suppose you want to model a spotlight that has some nonuniform pattern - brighter in the center, perhaps, or noncircular, because of flaps or lenses that modify the shape of the beam. You can emulate shining such a light on a flat surface by making a texture map that corresponds to the shape and intensity of a light, and then projecting it on the surface in question using projection transformations. Projecting the cone of light onto surfaces in the scene requires a perspective transformation (q ¹ 1), since the lights might shine on surfaces that aren't perpendicular to them. A second perspective transformation occurs because the viewer sees the scene from a different (but perspective) point of view. (See "Plate 27" in Appendix I for an example, and see "Fast Shadows and Lighting Effects Using Texture Mapping" by Mark Segal, Carl Korobkin, Rolf van Widenfelt, Jim Foran, and Paul Haeberli, SIGGRAPH 1992 Proceedings, (Computer Graphics, 26:2, July 1992, p. 249252) for more details.)

Another example might arise if the texture map to be applied comes from a photograph that itself was taken in perspective. As with spotlights, the final view depends on the combination of two perspective transformations.


Chapter 10 - OpenGL Programming Guide

OpenGL Programming Guide
Programming Guide > Chapter 10

Chapter 10
The Framebuffer

Chapter Objectives

After reading this chapter, you'll be able to do the following:

  • Understand what buffers make up the framebuffer and how they're used
  • Clear selected buffers and enable them for writing
  • Control the parameters of the scissoring, alpha, stencil, and depth-buffer tests that are applied to pixels
  • Perform dithering and logical operations
  • Use the accumulation buffer for such purposes as scene antialiasing

An important goal of almost every graphics program is to draw pictures on the screen. The screen is composed of a rectangular array of pixels, each capable of displaying a tiny square of color at that point in the image. After the rasterization stage (including texturing and fog), the data are not yet pixels, but are fragments. Each fragment has coordinate data which corresponds to a pixel, as well as color and depth values. Then each fragment undergoes a series of tests and operations, some of which have been previously described (See "Blending" in Chapter 6) and others that are discussed in this chapter.

If the tests and operations are survived, the fragment values are ready to become pixels. To draw these pixels, you need to know what color they are, which is the information that's stored in the color buffer. Whenever data is stored uniformly for each pixel, such storage for all the pixels is called a buffer. Different buffers might contain different amounts of data per pixel, but within a given buffer, each pixel is assigned the same amount of data. A buffer that stores a single bit of information about pixels is called a bitplane.

As shown in Figure 10-1, the lower-left pixel in an OpenGL window is pixel (0, 0), corresponding to the window coordinates of the lower-left corner of the 1 ´ 1 region occupied by this pixel. In general, pixel (x, y) fills the region bounded by x on the left, x+1 on the right, y on the bottom, and y+1 on the top.

Figure 10-1 : Region Occupied by a Pixel

As an example of a buffer, let's look more closely at the color buffer, which holds the color information that's to be displayed on the screen. Assume that the screen is 1280 pixels wide and 1024 pixels high and that it's a full 24-bit color screen - in other words, there are 224 (or 16,777,216) different colors that can be displayed. Since 24 bits translates to 3 bytes (8 bits/byte), the color buffer in this example has to store at least 3 bytes of data for each of the 1,310,720 (1280*1024) pixels on the screen. A particular hardware system might have more or fewer pixels on the physical screen as well as more or less color data per pixel. Any particular color buffer, however, has the same amount of data saved for each pixel on the screen.

The color buffer is only one of several buffers that hold information about a pixel. For example, in "A Hidden-Surface Removal Survival Kit" in Chapter 5, you learned that the depth buffer holds depth information for each pixel. The color buffer itself can consist of several subbuffers. The framebuffer on a system comprises all of these buffers. With the exception of the color buffer(s), you don't view these other buffers directly; instead, you use them to perform such tasks as hidden-surface elimination, antialiasing of an entire scene, stenciling, drawing smooth motion, and other operations.

This chapter describes all the buffers that can exist in an OpenGL implementation and how they're used. It also discusses the series of tests and pixel operations that are performed before any data is written to the viewable color buffer. Finally, it explains how to use the accumulation buffer, which is used to accumulate images that are drawn into the color buffer. This chapter has the following major sections.

  • "Buffers and Their Uses" describes the possible buffers, what they're for, and how to clear them and enable them for writing.
  • "Testing and Operating on Fragments" explains the scissoring, alpha, stencil, and depth-buffer tests that occur after a pixel's position and color have been calculated but before this information is drawn on the screen. Several operations - blending, dithering, and logical operations - can also be performed before a fragment updates the screen.
  • "The Accumulation Buffer" describes how to perform several advanced techniques using the accumulation buffer. These techniques include antialiasing an entire scene, using motion blur, and simulating photographic depth of field.

Buffers and Their Uses

An OpenGL system can manipulate the following buffers:

  • Color buffers: front-left, front-right, back-left, back-right, and any number of auxiliary color buffers
  • Depth buffer
  • Stencil buffer
  • Accumulation buffer

Your particular OpenGL implementation determines which buffers are available and how many bits per pixel each holds. Additionally, you can have multiple visuals, or window types, that have different buffers available. Table 10-1 lists the parameters to use with glGetIntegerv() to query your OpenGL system about per-pixel buffer storage for a particular visual.

Note: If you're using the X Window System, you're guaranteed, at a minimum, to have a visual with one color buffer for use in RGBA mode with associated stencil, depth, and accumulation buffers that have color components of nonzero size. Also, if your X Window System implementation supports a Pseudo-Color visual, you are also guaranteed to have one OpenGL visual that has a color buffer for use in color-index mode with associated depth and stencil buffers. You'll probably want to use glXGetConfig() to query your visuals; see Appendix C and the OpenGL Reference Manual for more information about this routine.

Table 10-1 : Query Parameters for Per-Pixel Buffer Storage

Parameter | Meaning
GL_RED_BITS, GL_GREEN_BITS, GL_BLUE_BITS, GL_ALPHA_BITS | Number of bits per R, G, B, or A component in the color buffers
GL_INDEX_BITS | Number of bits per index in the color buffers
GL_DEPTH_BITS | Number of bits per pixel in the depth buffer
GL_STENCIL_BITS | Number of bits per pixel in the stencil buffer
GL_ACCUM_RED_BITS, GL_ACCUM_GREEN_BITS, GL_ACCUM_BLUE_BITS, GL_ACCUM_ALPHA_BITS | Number of bits per R, G, B, or A component in the accumulation buffer

Color Buffers

The color buffers are the ones to which you usually draw. They contain either color-index or RGB color data and may also contain alpha values. An OpenGL implementation that supports stereoscopic viewing has left and right color buffers for the left and right stereo images. If stereo isn't supported, only the left buffers are used. Similarly, double-buffered systems have front and back buffers, and a single-buffered system has the front buffers only. Every OpenGL implementation must provide a front-left color buffer.

Optional, nondisplayable auxiliary color buffers may also be supported. OpenGL doesn't specify any particular uses for these buffers, so you can define and use them however you please. For example, you might use them for saving an image that you use repeatedly. Then rather than redrawing the image, you can just copy it from an auxiliary buffer into the usual color buffers. (See the description of glCopyPixels() in "Reading, Writing, and Copying Pixel Data" in Chapter 8 for more information about how to do this.)

You can use GL_STEREO or GL_DOUBLEBUFFER with glGetBooleanv() to find out if your system supports stereo (that is, has left and right buffers) or double-buffering (has front and back buffers). To find out how many, if any, auxiliary buffers are present, use glGetIntegerv() with GL_AUX_BUFFERS.

Depth Buffer

The depth buffer stores a depth value for each pixel. As described in "A Hidden-Surface Removal Survival Kit" in Chapter 5, depth is usually measured in terms of distance to the eye, so pixels with larger depth-buffer values are overwritten by pixels with smaller values. This is just a useful convention, however, and the depth buffer's behavior can be modified as described in "Depth Test." The depth buffer is sometimes called the z buffer (the z comes from the fact that x and y values measure horizontal and vertical displacement on the screen, and the z value measures distance perpendicular to the screen).

Stencil Buffer

One use for the stencil buffer is to restrict drawing to certain portions of the screen, just as a cardboard stencil can be used with a can of spray paint to make fairly precise painted images. For example, if you want to draw an image as it would appear through an odd-shaped windshield, you can store an image of the windshield's shape in the stencil buffer, and then draw the entire scene. The stencil buffer prevents anything that wouldn't be visible through the windshield from being drawn. Thus, if your application is a driving simulation, you can draw all the instruments and other items inside the automobile once, and as the car moves, only the outside scene need be updated.

Accumulation Buffer

The accumulation buffer holds RGBA color data just like the color buffers do in RGBA mode. (The results of using the accumulation buffer in color-index mode are undefined.) It's typically used for accumulating a series of images into a final, composite image. With this method, you can perform operations like scene antialiasing by supersampling an image and then averaging the samples to produce the values that are finally painted into the pixels of the color buffers. You don't draw directly into the accumulation buffer; accumulation operations are always performed in rectangular blocks, which are usually transfers of data to or from a color buffer.

Clearing Buffers

In graphics programs, clearing the screen (or any of the buffers) is typically one of the most expensive operations you can perform - on a 1280 ´ 1024 monitor, it requires touching well over a million pixels. For simple graphics applications, the clear operation can take more time than the rest of the drawing. If you need to clear not only the color buffer but also the depth and stencil buffers, the clear operation can be three times as expensive.

To address this problem, some machines have hardware that can clear more than one buffer at once. The OpenGL clearing commands are structured to take advantage of architectures like this. First, you specify the values to be written into each buffer to be cleared. Then you issue a single command to perform the clear operation, passing in a list of all the buffers to be cleared. If the hardware is capable of simultaneous clears, they all occur at once; otherwise, each buffer is cleared sequentially.

The following commands set the clearing values for each buffer.

void glClearColor(GLclampf red, GLclampf green, GLclampf blue,
GLclampf alpha);
void glClearIndex(GLfloat index);
void glClearDepth(GLclampd depth);
void glClearStencil(GLint s);
void glClearAccum(GLfloat red, GLfloat green, GLfloat blue,
GLfloat alpha);
Specifies the current clearing values for the color buffer (in RGBA mode), the color buffer (in color-index mode), the depth buffer, the stencil buffer, and the accumulation buffer. The GLclampf and GLclampd types (clamped GLfloat and clamped GLdouble) are clamped to be between 0.0 and 1.0. The default depth-clearing value is 1.0; all the other default clearing values are 0. The values set with the clear commands remain in effect until they're changed by another call to the same command.

After you've selected your clearing values and you're ready to clear the buffers, use glClear().

void glClear(GLbitfield mask);
Clears the specified buffers. The value of mask is the bitwise logical OR of some combination of GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT, GL_STENCIL_BUFFER_BIT, and GL_ACCUM_BUFFER_BIT to identify which buffers are to be cleared. GL_COLOR_BUFFER_BIT clears either the RGBA color or the color-index buffer, depending on the mode of the system at the time. When you clear the color or color-index buffer, all the color buffers that are enabled for writing (see the next section) are cleared. The pixel ownership test, scissor test, and dithering, if enabled, are applied to the clearing operation. Masking operations, such as glColorMask() and glIndexMask(), are also effective. The alpha test, stencil test, and depth test do not affect the operation of glClear().

Selecting Color Buffers for Writing and Reading

The results of a drawing or reading operation can go into or come from any of the color buffers: front, back, front-left, back-left, front-right, back-right, or any of the auxiliary buffers. You can choose an individual buffer to be the drawing or reading target. For drawing, you can also set the target to draw into more than one buffer at the same time. You use glDrawBuffer() to select the buffers to be written and glReadBuffer() to select the buffer as the source for glReadPixels(), glCopyPixels(), glCopyTexImage*(), and glCopyTexSubImage*().

If you are using double-buffering, you usually want to draw only in the back buffer (and swap the buffers when you're finished drawing). In some situations, you might want to treat a double-buffered window as though it were single-buffered by calling glDrawBuffer() to enable you to draw to both front and back buffers at the same time.

glDrawBuffer() is also used to select buffers to render stereo images (GL*LEFT and GL*RIGHT) and to render into auxiliary buffers (GL_AUXi).

void glDrawBuffer(GLenum mode);
Selects the color buffers enabled for writing or clearing. Disables buffers enabled by previous calls to glDrawBuffer(). More than one buffer may be enabled at one time. The value of mode can be one of the following:

GL_FRONT | GL_FRONT_LEFT | GL_AUXi
GL_BACK | GL_FRONT_RIGHT | GL_FRONT_AND_BACK
GL_LEFT | GL_BACK_LEFT | GL_NONE
GL_RIGHT | GL_BACK_RIGHT |

Arguments that omit LEFT or RIGHT refer to both the left and right buffers; similarly, arguments that omit FRONT or BACK refer to both. The i in GL_AUXi is a digit identifying a particular auxiliary buffer.
By default, mode is GL_FRONT for single-buffered contexts and GL_BACK for double-buffered contexts.

Note: You can enable drawing to nonexistent buffers as long as you enable drawing to at least one buffer that does exist. If none of the specified buffers exist, an error results.

void glReadBuffer(GLenum mode);
Selects the color buffer enabled as the source for reading pixels for subsequent calls to glReadPixels(), glCopyPixels(), glCopyTexImage*(), and glCopyTexSubImage*(). Disables buffers enabled by previous calls to glReadBuffer(). The value of mode can be one of the following:

GL_FRONT | GL_FRONT_LEFT | GL_AUXi
GL_BACK | GL_FRONT_RIGHT |
GL_LEFT | GL_BACK_LEFT |
GL_RIGHT | GL_BACK_RIGHT |

By default, mode is GL_FRONT for single-buffered contexts and GL_BACK for double-buffered contexts.

Note: You must enable reading from a buffer that does exist or an error results.

Masking Buffers

Before OpenGL writes data into the enabled color, depth, or stencil buffers, a masking operation is applied to the data, as specified with one of the following commands. A bitwise logical AND is performed with each mask and the corresponding data to be written.

void glIndexMask(GLuint mask);
void glColorMask(GLboolean red, GLboolean green, GLboolean blue,
GLboolean alpha);
void glDepthMask(GLboolean flag);
void glStencilMask(GLuint mask);
Sets the masks used to control writing into the indicated buffers. The mask set by glIndexMask() applies only in color-index mode. If a 1 appears in mask, the corresponding bit in the color-index buffer is written; where a 0 appears, the bit isn't written. Similarly, glColorMask() affects drawing in RGBA mode only. The red, green, blue, and alpha values control whether the corresponding component is written. (GL_TRUE means it is written.) If flag is GL_TRUE for glDepthMask(), the depth buffer is enabled for writing; otherwise, it's disabled. The mask for glStencilMask() is used for stencil data in the same way as the mask is used for color-index data in glIndexMask(). The default values of all the GLboolean masks are GL_TRUE, and the default values for the two GLuint masks are all 1's.

You can do plenty of tricks with color masking in color-index mode. For example, you can use each bit in the index as a different layer and set up interactions between arbitrary layers with appropriate settings of the color map. You can create overlays and underlays, and do so-called color-map animations. (See Chapter 14 for examples of using color masking.) Masking in RGBA mode is useful less often, but you can use it for loading separate image files into the red, green, and blue bitplanes, for example.

You've seen one use for disabling the depth buffer in "Three-Dimensional Blending with the Depth Buffer" in Chapter 6. Disabling the depth buffer for writing can also be useful if a common background is desired for a series of frames, and you want to add some features that may be obscured by parts of the background. For example, suppose your background is a forest, and you would like to draw repeated frames with the same trees, but with objects moving among them. After the trees are drawn with their depths recorded in the depth buffer, then the image of the trees is saved, and the new items are drawn with the depth buffer disabled for writing. As long as the new items don't overlap each other, the picture is correct. To draw the next frame, restore the image of the trees and continue. You don't need to restore the values in the depth buffer. This trick is most useful if the background is extremely complex - so complex that it's much faster just to recopy the image into the color buffer than to recompute it from the geometry.

Masking the stencil buffer can allow you to use a multiple-bit stencil buffer to hold multiple stencils (one per bit). You might use this technique to perform capping as explained in "Stencil Test" or to implement the Game of Life as described in "Life in the Stencil Buffer" in Chapter 14.

Note: The mask specified by glStencilMask() controls which stencil bitplanes are written. This mask isn't related to the mask that's specified as the third parameter of glStencilFunc(), which specifies which bitplanes are considered by the stencil function.

Testing and Operating on Fragments

When you draw geometry, text, or images on the screen, OpenGL performs several calculations to rotate, translate, scale, determine the lighting, project the object(s) into perspective, figure out which pixels in the window are affected, and determine what colors those pixels should be drawn. Many of the earlier chapters in this book give some information about how to control these operations. After OpenGL determines that an individual fragment should be generated and what its color should be, several processing stages remain that control how and whether the fragment is drawn as a pixel into the framebuffer. For example, if it's outside a rectangular region or if it's farther from the viewpoint than the pixel that's already in the framebuffer, it isn't drawn. In another stage, the fragment's color is blended with the color of the pixel already in the framebuffer.

This section describes both the complete set of tests that a fragment must pass before it goes into the framebuffer and the possible final operations that can be performed on the fragment as it's written. The tests and operations occur in the following order; if a fragment is eliminated in an early test, none of the later tests or operations take place.

Scissor test
Alpha test
Stencil test
Depth test
Blending
Dithering
Logical operation

Each of these tests and operations is described in detail in the following sections.

Scissor Test

You can define a rectangular portion of your window and restrict drawing to take place within it by using the glScissor() command. If a fragment lies inside the rectangle, it passes the scissor test.

void glScissor(GLint x, GLint y, GLsizei width, GLsizei height);
Sets the location and size of the scissor rectangle (also known as the scissor box). The parameters define the lower-left corner (x, y), and the width and height of the rectangle. Pixels that lie inside the rectangle pass the scissor test. Scissoring is enabled and disabled by passing GL_SCISSOR_TEST to glEnable() and glDisable(). By default, the rectangle matches the size of the window and scissoring is disabled.

The scissor test is just a version of a stencil test using a rectangular region of the screen. It's fairly easy to create a blindingly fast hardware implementation of scissoring, while a given system might be much slower at stenciling - perhaps because the stenciling is performed in software.

Advanced

An advanced use of scissoring is performing nonlinear projection. First divide the window into a regular grid of subregions, specifying viewport and scissor parameters that limit rendering to one region at a time. Then project the entire scene to each region using a different projection matrix.

To determine whether scissoring is enabled and to obtain the values that define the scissor rectangle, you can use GL_SCISSOR_TEST with glIsEnabled() and GL_SCISSOR_BOX with glGetIntegerv().

Alpha Test

In RGBA mode, the alpha test allows you to accept or reject a fragment based on its alpha value. The alpha test is enabled and disabled by passing GL_ALPHA_TEST to glEnable() and glDisable(). To determine whether the alpha test is enabled, use GL_ALPHA_TEST with glIsEnabled().

If enabled, the test compares the incoming alpha value with a reference value. The fragment is accepted or rejected depending on the result of the comparison. Both the reference value and the comparison function are set with glAlphaFunc(). By default, the reference value is zero, the comparison function is GL_ALWAYS, and the alpha test is disabled. To obtain the alpha comparison function or reference value, use GL_ALPHA_TEST_FUNC or GL_ALPHA_TEST_REF with glGetIntegerv().

void glAlphaFunc(GLenum func, GLclampf ref);
Sets the reference value and comparison function for the alpha test. The reference value ref is clamped to be between zero and one. The possible values for func and their meaning are listed in Table 10-2.
Table 10-2 : glAlphaFunc() Parameter Values (continued)
Parameter | Meaning
GL_NEVER | Never accept the fragment
GL_ALWAYS | Always accept the fragment
GL_LESS | Accept fragment if fragment alpha < reference alpha
GL_LEQUAL | Accept fragment if fragment alpha £ reference alpha
GL_EQUAL | Accept fragment if fragment alpha = reference alpha
GL_GEQUAL | Accept fragment if fragment alpha ³ reference alpha
GL_GREATER | Accept fragment if fragment alpha > reference alpha
GL_NOTEQUAL | Accept fragment if fragment alpha ¹ reference alpha

One application for the alpha test is to implement a transparency algorithm. Render your entire scene twice, the first time accepting only fragments with alpha values of one, and the second time accepting fragments with alpha values that aren't equal to one. Turn the depth buffer on during both passes, but disable depth buffer writing during the second pass.

Another use might be to make decals with texture maps where you can see through certain parts of the decals. Set the alphas in the decals to 0.0 where you want to see through, set them to 1.0 otherwise, set the reference value to 0.5 (or anything between 0.0 and 1.0), and set the comparison function to GL_GREATER. The decal has see-through parts, and the values in the depth buffer aren't affected. This technique, called billboarding, is described in "Sample Uses of Blending" in Chapter 6.

Stencil Test

The stencil test takes place only if there is a stencil buffer. (If there is no stencil buffer, the stencil test always passes.) Stenciling applies a test that compares a reference value with the value stored at a pixel in the stencil buffer. Depending on the result of the test, the value in the stencil buffer is modified. You can choose the particular comparison function used, the reference value, and the modification performed with the glStencilFunc() and glStencilOp() commands.

void glStencilFunc(GLenum func, GLint ref, GLuint mask);
Sets the comparison function (func), reference value (ref), and a mask (mask) for use with the stencil test. The reference value is compared to the value in the stencil buffer using the comparison function, but the comparison applies only to those bits where the corresponding bits of the mask are 1. The function can be GL_NEVER, GL_ALWAYS, GL_LESS, GL_LEQUAL, GL_EQUAL, GL_GEQUAL, GL_GREATER, or GL_NOTEQUAL. If it's GL_LESS, for example, then the fragment passes if ref is less than the value in the stencil buffer. If the stencil buffer contains s bitplanes, the low-order s bits of mask are bitwise ANDed with the value in the stencil buffer and with the reference value before the comparison is performed. The masked values are all interpreted as nonnegative values. The stencil test is enabled and disabled by passing GL_STENCIL_TEST to glEnable() and glDisable(). By default, func is GL_ALWAYS, ref is 0, mask is all 1's, and stenciling is disabled.
void glStencilOp(GLenum fail, GLenum zfail, GLenum zpass);
Specifies how the data in the stencil buffer is modified when a fragment passes or fails the stencil test. The three functions fail, zfail, and zpass can be GL_KEEP, GL_ZERO, GL_REPLACE, GL_INCR, GL_DECR, or GL_INVERT. They correspond to keeping the current value, replacing it with zero, replacing it with the reference value, incrementing it, decrementing it, and bitwise-inverting it. The result of the increment and decrement functions is clamped to lie between zero and the maximum unsigned integer value (2s-1 if the stencil buffer holds s bits). The fail function is applied if the fragment fails the stencil test; if it passes, then zfail is applied if the depth test fails and zpass if the depth test passes, or if no depth test is performed. (See "Depth Test." ) By default, all three stencil operations are GL_KEEP.

Stencil Queries

You can obtain the values for all six stencil-related parameters by using the query function glGetIntegerv() and one of the values shown in Table 10-3. You can also determine whether the stencil test is enabled by passing GL_STENCIL_TEST to glIsEnabled().

Table 10-3 : Query Values for the Stencil Test (continued)

Query Value | Meaning
GL_STENCIL_FUNC | Stencil function
GL_STENCIL_REF | Stencil reference value
GL_STENCIL_VALUE_MASK | Stencil mask
GL_STENCIL_FAIL | Stencil fail action
GL_STENCIL_PASS_DEPTH_FAIL | Stencil pass and depth buffer fail action
GL_STENCIL_PASS_DEPTH_PASS | Stencil pass and depth buffer pass action

Stencil Examples

Probably the most typical use of the stencil test is to mask out an irregularly shaped region of the screen to prevent drawing from occurring within it (as in the windshield example in "Buffers and Their Uses"). To do this, fill the stencil mask with zeros, and then draw the desired shape in the stencil buffer with 1's. You can't draw geometry directly into the stencil buffer, but you can achieve the same result by drawing into the color buffer and choosing a suitable value for the zpass function (such as GL_REPLACE). (You can use glDrawPixels() to draw pixel data directly into the stencil buffer.) Whenever drawing occurs, a value is also written into the stencil buffer (in this case, the reference value). To prevent the stencil-buffer drawing from affecting the contents of the color buffer, set the color mask to zero (or GL_FALSE). You might also want to disable writing into the depth buffer.

After you've defined the stencil area, set the reference value to one, and the comparison function such that the fragment passes if the reference value is equal to the stencil-plane value. During drawing, don't modify the contents of the stencil planes.

Example 10-1 demonstrates how to use the stencil test in this way. Two tori are drawn, with a diamond-shaped cutout in the center of the scene. Within the diamond-shaped stencil mask, a sphere is drawn. In this example, drawing into the stencil buffer takes place only when the window is redrawn, so the color buffer is cleared after the stencil mask has been created.

Example 10-1 : Using the Stencil Test: stencil.c

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <stdlib.h>

#define YELLOWMAT   1
#define BLUEMAT 2

void init (void)
{
   GLfloat yellow_diffuse[] = { 0.7, 0.7, 0.0, 1.0 };
   GLfloat yellow_specular[] = { 1.0, 1.0, 1.0, 1.0 };

   GLfloat blue_diffuse[] = { 0.1, 0.1, 0.7, 1.0 };
   GLfloat blue_specular[] = { 0.1, 1.0, 1.0, 1.0 };

   GLfloat position_one[] = { 1.0, 1.0, 1.0, 0.0 };

   glNewList(YELLOWMAT, GL_COMPILE);
   glMaterialfv(GL_FRONT, GL_DIFFUSE, yellow_diffuse);
   glMaterialfv(GL_FRONT, GL_SPECULAR, yellow_specular);
   glMaterialf(GL_FRONT, GL_SHININESS, 64.0);
   glEndList();

   glNewList(BLUEMAT, GL_COMPILE);
   glMaterialfv(GL_FRONT, GL_DIFFUSE, blue_diffuse);
   glMaterialfv(GL_FRONT, GL_SPECULAR, blue_specular);
   glMaterialf(GL_FRONT, GL_SHININESS, 45.0);
   glEndList();

   glLightfv(GL_LIGHT0, GL_POSITION, position_one);

   glEnable(GL_LIGHT0);
   glEnable(GL_LIGHTING);
   glEnable(GL_DEPTH_TEST);

   glClearStencil(0x0);
   glEnable(GL_STENCIL_TEST);
}

/* Draw a sphere in a diamond-shaped section in the
 * middle of a window with 2 tori.
 */
void display(void)
{
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

/* draw blue sphere where the stencil is 1 */
   glStencilFunc (GL_EQUAL, 0x1, 0x1);
   glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
   glCallList (BLUEMAT);
   glutSolidSphere (0.5, 15, 15);

/* draw the tori where the stencil is not 1 */
   glStencilFunc (GL_NOTEQUAL, 0x1, 0x1);
   glPushMatrix();
      glRotatef (45.0, 0.0, 0.0, 1.0);
      glRotatef (45.0, 0.0, 1.0, 0.0);
      glCallList (YELLOWMAT);
      glutSolidTorus (0.275, 0.85, 15, 15);
      glPushMatrix();
         glRotatef (90.0, 1.0, 0.0, 0.0);
         glutSolidTorus (0.275, 0.85, 15, 15);
      glPopMatrix();
   glPopMatrix();
}

/*  Whenever the window is reshaped, redefine the
 *  coordinate system and redraw the stencil area.
 */
void reshape(int w, int h)
{
   glViewport(0, 0, (GLsizei) w, (GLsizei) h);

/* create a diamond shaped stencil area */
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   if (w <= h)
      gluOrtho2D(-3.0, 3.0, -3.0*(GLfloat)h/(GLfloat)w,
                 3.0*(GLfloat)h/(GLfloat)w);
   else
      gluOrtho2D(-3.0*(GLfloat)w/(GLfloat)h,
                 3.0*(GLfloat)w/(GLfloat)h, -3.0, 3.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();

   glClear(GL_STENCIL_BUFFER_BIT);
   glStencilFunc (GL_ALWAYS, 0x1, 0x1);
   glStencilOp (GL_REPLACE, GL_REPLACE, GL_REPLACE);
   glBegin(GL_QUADS);
      glVertex2f (-1.0, 0.0);
      glVertex2f (0.0, 1.0);
      glVertex2f (1.0, 0.0);
      glVertex2f (0.0, -1.0);
   glEnd();

   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluPerspective(45.0, (GLfloat) w/(GLfloat) h, 3.0, 7.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
   glTranslatef(0.0, 0.0, -5.0);
}

/* Main Loop
 * Be certain to request stencil bits.
 */
int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB
                        | GLUT_DEPTH | GLUT_STENCIL);
   glutInitWindowSize (400, 400);
   glutInitWindowPosition (100, 100);
   glutCreateWindow (argv[0]);
   init ();
   glutReshapeFunc(reshape);
   glutDisplayFunc(display);
   glutMainLoop();
   return 0;
}

The following examples illustrate other uses of the stencil test. (See Chapter 14 for additional ideas.)

  • Capping - Suppose you're drawing a closed convex object (or several of them, as long as they don't intersect or enclose each other) made up of several polygons, and you have a clipping plane that may or may not slice off a piece of it. Suppose that if the plane does intersect the object, you want to cap the object with some constant-colored surface, rather than seeing the inside of it. To do this, clear the stencil buffer to zeros, and begin drawing with stenciling enabled and the stencil comparison function set to always accept fragments. Invert the value in the stencil planes each time a fragment is accepted. After all the objects are drawn, regions of the screen where no capping is required have zeros in the stencil planes, and regions requiring capping are nonzero. Reset the stencil function so that it draws only where the stencil value is nonzero, and draw a large polygon of the capping color across the entire screen.
  • Overlapping translucent polygons - Suppose you have a translucent surface that's made up of polygons that overlap slightly. If you simply use alpha blending, portions of the underlying objects are covered by more than one transparent surface, which doesn't look right. Use the stencil planes to make sure that each fragment is covered by at most one portion of the transparent surface. Do this by clearing the stencil planes to zeros, drawing only when the stencil plane is zero, and incrementing the value in the stencil plane when you draw.
  • Stippling - Suppose you want to draw an image with a stipple pattern. (See "Displaying Points, Lines, and Polygons" in Chapter 2 for more information about stippling.) You can do this by writing the stipple pattern into the stencil buffer, and then drawing conditionally on the contents of the stencil buffer. After the original stipple pattern is drawn, the stencil buffer isn't altered while drawing the image, so the object gets stippled by the pattern in the stencil planes.

Depth Test

For each pixel on the screen, the depth buffer keeps track of the distance between the viewpoint and the object occupying that pixel. Then if the specified depth test passes, the incoming depth value replaces the one already in the depth buffer.

The depth buffer is generally used for hidden-surface elimination. If a new candidate color for that pixel appears, it's drawn only if the corresponding object is closer than the previous object. In this way, after the entire scene has been rendered, only objects that aren't obscured by other items remain. Initially, the clearing value for the depth buffer is a value that's as far from the viewpoint as possible, so the depth of any object is nearer than that value. If this is how you want to use the depth buffer, you simply have to enable it by passing GL_DEPTH_TEST to glEnable() and remember to clear the depth buffer before you redraw each frame. (See "Clearing Buffers.") You can also choose a different comparison function for the depth test with glDepthFunc().

void glDepthFunc(GLenum func);
Sets the comparison function for the depth test. The value for func must be GL_NEVER, GL_ALWAYS, GL_LESS, GL_LEQUAL, GL_EQUAL, GL_GEQUAL, GL_GREATER, or GL_NOTEQUAL. An incoming fragment passes the depth test if its z value has the specified relation to the value already stored in the depth buffer. The default is GL_LESS, which means that an incoming fragment passes the test if its z value is less than that already stored in the depth buffer. In this case, the z value represents the distance from the object to the viewpoint, and smaller values mean the corresponding objects are closer to the viewpoint.

Blending, Dithering, and Logical Operations

Once an incoming fragment has passed all the tests described in the previous section, it can be combined with the current contents of the color buffer in one of several ways. The simplest way, which is also the default, is to overwrite the existing values. Alternatively, if you're using RGBA mode and you want the fragment to be translucent or antialiased, you might average its value with the value already in the buffer (blending). On systems with a small number of available colors, you might want to dither color values to increase the number of colors available at the cost of a loss in resolution. In the final stage, you can use arbitrary bitwise logical operations to combine the incoming fragment and the pixel that's already written.

Blending

Blending combines the incoming fragment's R, G, B, and alpha values with those of the pixel already stored at the location. Different blending operations can be applied, and the blending that occurs depends on the values of the incoming alpha value and the alpha value (if any) stored at the pixel. (See "Blending" in Chapter 6 for an extensive discussion of this topic.)

Dithering

On systems with a small number of color bitplanes, you can improve the color resolution at the expense of spatial resolution by dithering the color in the image. Dithering is like halftoning in newspapers. Although The New York Times has only two colors - black and white - it can show photographs by representing the shades of gray with combinations of black and white dots. Comparing a newspaper image of a photo (having no shades of gray) with the original photo (with grayscale) makes the loss of spatial resolution obvious. Similarly, systems with a small number of color bitplanes may dither values of red, green, and blue on neighboring pixels for the perception of a wider range of colors.

The dithering operation that takes place is hardware-dependent; all OpenGL allows you to do is to turn it on and off. In fact, on some machines, enabling dithering might do nothing at all, which makes sense if the machine already has high color resolution. To enable and disable dithering, pass GL_DITHER to glEnable() and glDisable(). Dithering is enabled by default.

Dithering applies in both RGBA and color-index mode. The colors or color indices alternate in some hardware-dependent way between the two nearest possibilities. For example, in color-index mode, if dithering is enabled and the color index to be painted is 4.4, then 60% of the pixels may be painted with index 4 and 40% of the pixels with index 5. (Many dithering algorithms are possible, but a dithered value produced by any algorithm must depend upon only the incoming value and the fragment's x and y coordinates.) In RGBA mode, dithering is performed separately for each component (including alpha). To use dithering in color-index mode, you generally need to arrange the colors in the color map appropriately in ramps, otherwise, bizarre images might result.

Logical Operations

The final operation on a fragment is the logical operation, such as an OR, XOR, or INVERT, which is applied to the incoming fragment values (source) and/or those currently in the color buffer (destination). Such fragment operations are especially useful on bit-blt-type machines, on which the primary graphics operation is copying a rectangle of data from one place in the window to another, from the window to processor memory, or from memory to the window. Typically, the copy doesn't write the data directly into memory but instead allows you to perform an arbitrary logical operation on the incoming data and the data already present; then it replaces the existing data with the results of the operation.

Since this process can be implemented fairly cheaply in hardware, many such machines are available. As an example of using a logical operation, XOR can be used to draw on an image in an undoable way; simply XOR the same drawing again, and the original image is restored. As another example, when using color-index mode, the color indices can be interpreted as bit patterns. Then you can compose an image as combinations of drawings on different layers, use writemasks to limit drawing to different sets of bitplanes, and perform logical operations to modify different layers.

You enable and disable logical operations by passing GL_INDEX_LOGIC_OP or GL_COLOR_LOGIC_OP to glEnable() and glDisable() for color-index mode or RGBA mode, respectively. You also must choose among the sixteen logical operations with glLogicOp(), or you'll just get the effect of the default value, GL_COPY. (For backward compatibility with OpenGL Version 1.0, glEnable(GL_LOGIC_OP) also enables logical operation in color-index mode.)

void glLogicOp(GLenum opcode);
Selects the logical operation to be performed, given an incoming (source) fragment and the pixel currently stored in the color buffer (destination). Table 10-4 shows the possible values for opcode and their meaning (s represents source and d destination). The default value is GL_COPY.
Table 10-4 : Sixteen Logical Operations
Parameter | Operation | Parameter | Operation
GL_CLEAR | 0 | GL_AND | s & d
GL_COPY | s | GL_OR | s | d
GL_NOOP | d | GL_NAND | !(s & d)
GL_SET | 1 | GL_NOR | !(s | d)
GL_COPY_INVERTED | !s | GL_XOR | s XOR d
GL_INVERT | !d | GL_EQUIV | !(s XOR d)
GL_AND_REVERSE | s & !d | GL_AND_INVERTED | !s & d
GL_OR_REVERSE | s | !d | GL_OR_INVERTED | !s | d

The Accumulation Buffer

Advanced

The accumulation buffer can be used for such things as scene antialiasing, motion blur, simulating photographic depth of field, and calculating the soft shadows that result from multiple light sources. Other techniques are possible, especially in combination with some of the other buffers. (See The Accumulation Buffer: Hardware Support for High-Quality Rendering by Paul Haeberli and Kurt Akeley (SIGGRAPH 1990 Proceedings, p. 309318) for more information on the uses for the accumulation buffer.)

OpenGL graphics operations don't write directly into the accumulation buffer. Typically, a series of images is generated in one of the standard color buffers, and these are accumulated, one at a time, into the accumulation buffer. When the accumulation is finished, the result is copied back into a color buffer for viewing. To reduce rounding errors, the accumulation buffer may have higher precision (more bits per color) than the standard color buffers. Rendering a scene several times obviously takes longer than rendering it once, but the result is higher quality. You can decide what trade-off between quality and rendering time is appropriate for your application.

You can use the accumulation buffer the same way a photographer can use film for multiple exposures. A photographer typically creates a multiple exposure by taking several pictures of the same scene without advancing the film. If anything in the scene moves, that object appears blurred. Not surprisingly, a computer can do more with an image than a photographer can do with a camera. For example, a computer has exquisite control over the viewpoint, but a photographer can't shake a camera a predictable and controlled amount. (See "Clearing Buffers" for information about how to clear the accumulation buffer; use glAccum() to control it.)

void glAccum(GLenum op, GLfloat value);
Controls the accumulation buffer. The op parameter selects the operation, and value is a number to be used in that operation. The possible operations are GL_ACCUM, GL_LOAD, GL_RETURN, GL_ADD, and GL_MULT.
  • GL_ACCUM reads each pixel from the buffer currently selected for reading with glReadBuffer(), multiplies the R, G, B, and alpha values by value, and adds the result to the accumulation buffer.
  • GL_LOAD does the same thing, except that the values replace those in the accumulation buffer rather than being added to them.
  • GL_RETURN takes values from the accumulation buffer, multiplies them by value, and places the result in the color buffer(s) enabled for writing.
  • GL_ADD and GL_MULT simply add or multiply the value of each pixel in the accumulation buffer by value and then return it to the accumulation buffer. For GL_MULT, value is clamped to be in the range [-1.0,1.0]. For GL_ADD, no clamping occurs.

Scene Antialiasing

To perform scene antialiasing, first clear the accumulation buffer and enable the front buffer for reading and writing. Then loop several times (say, n) through code that jitters and draws the image (jittering is moving the image to a slightly different position), accumulating the data with

glAccum(GL_ACCUM, 1.0/n);

and finally calling

glAccum(GL_RETURN, 1.0);

Note that this method is a bit faster if, on the first pass through the loop, GL_LOAD is used and clearing the accumulation buffer is omitted. See Table 10-5 for possible jittering values. With this code, the image is drawn n times before the final image is drawn. If you want to avoid showing the user the intermediate images, draw into a color buffer that's not displayed, accumulate from that, and use the GL_RETURN call to draw into a displayed buffer (or into a back buffer that you subsequently swap to the front).

You could instead present a user interface that shows the viewed image improving as each additional piece is accumulated and that allows the user to halt the process when the image is good enough. To accomplish this, in the loop that draws successive images, call glAccum() with GL_RETURN after each accumulation, using 16.0/1.0, 16.0/2.0, 16.0/3.0, ... as the second argument. With this technique, after one pass, 1/16 of the final image is shown, after two passes, 2/16 is shown, and so on. After the GL_RETURN, the code should check to see if the user wants to interrupt the process. This interface is slightly slower, since the resultant image must be copied in after each pass.

To decide what n should be, you need to trade off speed (the more times you draw the scene, the longer it takes to obtain the final image) and quality (the more times you draw the scene, the smoother it gets, until you make maximum use of the accumulation buffer's resolution). "Plate 22" and "Plate 23" show improvements made using scene antialiasing.

Example 10-2 defines two routines for jittering that you might find useful: accPerspective() and accFrustum(). The routine accPerspective() is used in place of gluPerspective(), and the first four parameters of both routines are the same. To jitter the viewing frustum for scene antialiasing, pass the x and y jitter values (of less than one pixel) to the fifth and sixth parameters of accPerspective(). Also pass 0.0 for the seventh and eighth parameters to accPerspective() and a nonzero value for the ninth parameter (to prevent division by zero inside accPerspective()). These last three parameters are used for depth-of-field effects, which are described later in this chapter.

Example 10-2 : Routines for Jittering the Viewing Volume: accpersp.c

#define PI_ 3.14159265358979323846

void accFrustum(GLdouble left, GLdouble right, GLdouble bottom,
    GLdouble top, GLdouble near, GLdouble far, GLdouble pixdx,
    GLdouble pixdy, GLdouble eyedx, GLdouble eyedy,
    GLdouble focus)
{
    GLdouble xwsize, ywsize;
    GLdouble dx, dy;
    GLint viewport[4];

    glGetIntegerv (GL_VIEWPORT, viewport);

    xwsize = right - left;
    ywsize = top - bottom;
    dx = -(pixdx*xwsize/(GLdouble) viewport[2] +
            eyedx*near/focus);
    dy = -(pixdy*ywsize/(GLdouble) viewport[3] +
            eyedy*near/focus);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glFrustum (left + dx, right + dx, bottom + dy, top + dy,
        near, far);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef (-eyedx, -eyedy, 0.0);
}

void accPerspective(GLdouble fovy, GLdouble aspect,
    GLdouble near, GLdouble far, GLdouble pixdx, GLdouble pixdy,
    GLdouble eyedx, GLdouble eyedy, GLdouble focus)
{
    GLdouble fov2,left,right,bottom,top;
    fov2 = ((fovy*PI_) / 180.0) / 2.0;

    top = near / (fcos(fov2) / fsin(fov2));
    bottom = -top;
    right = top * aspect;
    left = -right;

    accFrustum (left, right, bottom, top, near, far,
        pixdx, pixdy, eyedx, eyedy, focus);
}

Example 10-3 uses these two routines to perform scene antialiasing.

Example 10-3 : Scene Antialiasing: accpersp.c

#include <GL/gl.h>
#include <GL/glu.h>
#include <stdlib.h>
#include <math.h>
#include <GL/glut.h>
#include "jitter.h"

void init(void)
{
   GLfloat mat_ambient[] = { 1.0, 1.0, 1.0, 1.0 };
   GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
   GLfloat light_position[] = { 0.0, 0.0, 10.0, 1.0 };
   GLfloat lm_ambient[] = { 0.2, 0.2, 0.2, 1.0 };

   glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
   glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
   glMaterialf(GL_FRONT, GL_SHININESS, 50.0);
   glLightfv(GL_LIGHT0, GL_POSITION, light_position);
   glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lm_ambient);

   glEnable(GL_LIGHTING);
   glEnable(GL_LIGHT0);
   glEnable(GL_DEPTH_TEST);
   glShadeModel (GL_FLAT);

   glClearColor(0.0, 0.0, 0.0, 0.0);
   glClearAccum(0.0, 0.0, 0.0, 0.0);
}

void displayObjects(void)
{
   GLfloat torus_diffuse[] = { 0.7, 0.7, 0.0, 1.0 };
   GLfloat cube_diffuse[] = { 0.0, 0.7, 0.7, 1.0 };
   GLfloat sphere_diffuse[] = { 0.7, 0.0, 0.7, 1.0 };
   GLfloat octa_diffuse[] = { 0.7, 0.4, 0.4, 1.0 };

   glPushMatrix ();
   glTranslatef (0.0, 0.0, -5.0);
   glRotatef (30.0, 1.0, 0.0, 0.0);

   glPushMatrix ();
   glTranslatef (-0.80, 0.35, 0.0);
   glRotatef (100.0, 1.0, 0.0, 0.0);
   glMaterialfv(GL_FRONT, GL_DIFFUSE, torus_diffuse);
   glutSolidTorus (0.275, 0.85, 16, 16);
   glPopMatrix ();

   glPushMatrix ();
   glTranslatef (-0.75, -0.50, 0.0);
   glRotatef (45.0, 0.0, 0.0, 1.0);
   glRotatef (45.0, 1.0, 0.0, 0.0);
   glMaterialfv(GL_FRONT, GL_DIFFUSE, cube_diffuse);
   glutSolidCube (1.5);
   glPopMatrix ();

   glPushMatrix ();
   glTranslatef (0.75, 0.60, 0.0);
   glRotatef (30.0, 1.0, 0.0, 0.0);
   glMaterialfv(GL_FRONT, GL_DIFFUSE, sphere_diffuse);
   glutSolidSphere (1.0, 16, 16);
   glPopMatrix ();

   glPushMatrix ();
   glTranslatef (0.70, -0.90, 0.25);
   glMaterialfv(GL_FRONT, GL_DIFFUSE, octa_diffuse);
   glutSolidOctahedron ();
   glPopMatrix ();

   glPopMatrix ();
}

#define ACSIZE  8

void display(void)
{
   GLint viewport[4];
   int jitter;

   glGetIntegerv (GL_VIEWPORT, viewport);

   glClear(GL_ACCUM_BUFFER_BIT);
   for (jitter = 0; jitter < ACSIZE; jitter++) {
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      accPerspective (50.0,
         (GLdouble) viewport[2]/(GLdouble) viewport[3],
         1.0, 15.0, j8[jitter].x, j8[jitter].y, 0.0, 0.0, 1.0);
      displayObjects ();
      glAccum(GL_ACCUM, 1.0/ACSIZE);
   }
   glAccum (GL_RETURN, 1.0);
   glFlush();
}

void reshape(int w, int h)
{
   glViewport(0, 0, (GLsizei) w, (GLsizei) h);
}

/*  Main Loop
 *  Be certain you request an accumulation buffer.
 */
int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB
                        | GLUT_ACCUM | GLUT_DEPTH);
   glutInitWindowSize (250, 250);
   glutInitWindowPosition (100, 100);
   glutCreateWindow (argv[0]);
   init();
   glutReshapeFunc(reshape);
   glutDisplayFunc(display);
   glutMainLoop();
   return 0;
}

You don't have to use a perspective projection to perform scene antialiasing. You can antialias a scene with orthographic projection simply by using glTranslate*() to jitter the scene. Keep in mind that glTranslate*() operates in world coordinates, but you want the apparent motion of the scene to be less than one pixel, measured in screen coordinates. Thus, you must reverse the world-coordinate mapping by calculating the jittering translation values, using its width or height in world coordinates divided by its viewport size. Then multiply that world-coordinate value by the amount of jitter to determine how much the scene should be moved in world coordinates to get a predictable jitter of less than one pixel. Example 10-4 shows how the display() and reshape() routines might look with a world-coordinate width and height of 4.5.

Example 10-4 : Jittering with an Orthographic Projection: accanti.c

#define ACSIZE  8

void display(void)
{
   GLint viewport[4];
   int jitter;

   glGetIntegerv (GL_VIEWPORT, viewport);

   glClear(GL_ACCUM_BUFFER_BIT);
   for (jitter = 0; jitter < ACSIZE; jitter++) {
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      glPushMatrix ();
/*      Note that 4.5 is the distance in world space between
 *      left and right and bottom and top.
 *      This formula converts fractional pixel movement to
 *      world coordinates.
 */
      glTranslatef (j8[jitter].x*4.5/viewport[2],
                    j8[jitter].y*4.5/viewport[3], 0.0);
      displayObjects ();
      glPopMatrix ();
      glAccum(GL_ACCUM, 1.0/ACSIZE);
   }
   glAccum (GL_RETURN, 1.0);
   glFlush();
}

void reshape(int w, int h)
{
   glViewport(0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   if (w <= h)
      glOrtho (-2.25, 2.25, -2.25*h/w, 2.25*h/w, -10.0, 10.0);
   else
      glOrtho (-2.25*w/h, 2.25*w/h, -2.25, 2.25, -10.0, 10.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
}

Motion Blur

Similar methods can be used to simulate motion blur, as shown in "Plate 7" in Appendix I and Figure 10-2. Suppose your scene has some stationary and some moving objects in it, and you want to make a motion-blurred image extending over a small interval of time. Set up the accumulation buffer in the same way, but instead of spatially jittering the images, jitter them temporally. The entire scene can be made successively dimmer by calling

glAccum (GL_MULT, decayFactor);

as the scene is drawn into the accumulation buffer, where decayFactor is a number from 0.0 to 1.0. Smaller numbers for decayFactor cause the object to appear to be moving faster. You can transfer the completed scene with the object's current position and "vapor trail" of previous positions from the accumulation buffer to the standard color buffer with

glAccum (GL_RETURN, 1.0);

The image looks correct even if the items move at different speeds, or if some of them are accelerated. As before, the more jitter points (temporal, in this case) you use, the better the final image, at least up to the point where you begin to lose resolution due to finite precision in the accumulation buffer. You can combine motion blur with antialiasing by jittering in both the spatial and temporal domains, but you pay for higher quality with longer rendering times.

Figure 10-2 : Motion-Blurred Object

Depth of Field

A photograph made with a camera is in perfect focus only for items lying on a single plane a certain distance from the film. The farther an item is from this plane, the more out of focus it is. The depth of field for a camera is a region about the plane of perfect focus where items are out of focus by a small enough amount.

Under normal conditions, everything you draw with OpenGL is in focus (unless your monitor's bad, in which case everything is out of focus). The accumulation buffer can be used to approximate what you would see in a photograph where items are more and more blurred as their distance from a plane of perfect focus increases. It isn't an exact simulation of the effects produced in a camera, but the result looks similar to what a camera would produce.

To achieve this result, draw the scene repeatedly using calls with different argument values to glFrustum(). Choose the arguments so that the position of the viewpoint varies slightly around its true position and so that each frustum shares a common rectangle that lies in the plane of perfect focus, as shown in Figure 10-3. The results of all the renderings should be averaged in the usual way using the accumulation buffer.

Figure 10-3 : Jittered Viewing Volume for Depth-of-Field Effects

"Plate 10" in Appendix I shows an image of five teapots drawn using the depth-of-field effect. The gold teapot (second from the left) is in focus, and the other teapots get progressively blurrier, depending upon their distance from the focal plane (gold teapot). The code to draw this image is shown in Example 10-5 (which assumes accPerspective() and accFrustum() are defined as described in Example 10-2). The scene is drawn eight times, each with a slightly jittered viewing volume, by calling accPerspective(). As you recall, with scene antialiasing, the fifth and sixth parameters jitter the viewing volumes in the x and y directions. For the depth-of-field effect, however, you want to jitter the volume while holding it stationary at the focal plane. The focal plane is the depth value defined by the ninth (last) parameter to accPerspective(), which is z = 5.0 in this example. The amount of blur is determined by multiplying the x and y jitter values (seventh and eighth parameters of accPerspective()) by a constant. Determining the constant is not a science; experiment with values until the depth of field is as pronounced as you want. (Note that in Example 10-5, the fifth and sixth parameters to accPerspective() are set to 0.0, so scene antialiasing is turned off.)

Example 10-5 : Depth-of-Field Effect: dof.c

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <stdlib.h>
#include <math.h>
#include "jitter.h"

void init(void)
{
   GLfloat ambient[] = { 0.0, 0.0, 0.0, 1.0 };
   GLfloat diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
   GLfloat specular[] = { 1.0, 1.0, 1.0, 1.0 };
   GLfloat position[] = { 0.0, 3.0, 3.0, 0.0 };

   GLfloat lmodel_ambient[] = { 0.2, 0.2, 0.2, 1.0 };
   GLfloat local_view[] = { 0.0 };

   glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
   glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
   glLightfv(GL_LIGHT0, GL_POSITION, position);

   glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
   glLightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER, local_view);

   glFrontFace (GL_CW);
   glEnable(GL_LIGHTING);
   glEnable(GL_LIGHT0);
   glEnable(GL_AUTO_NORMAL);
   glEnable(GL_NORMALIZE);
   glEnable(GL_DEPTH_TEST);

   glClearColor(0.0, 0.0, 0.0, 0.0);
   glClearAccum(0.0, 0.0, 0.0, 0.0);
/*  make teapot display list */
   teapotList = glGenLists(1);
   glNewList (teapotList, GL_COMPILE);
   glutSolidTeapot (0.5);
   glEndList ();
}

void renderTeapot (GLfloat x, GLfloat y, GLfloat z,
   GLfloat ambr, GLfloat ambg, GLfloat ambb,
   GLfloat difr, GLfloat difg, GLfloat difb,
   GLfloat specr, GLfloat specg, GLfloat specb, GLfloat shine)
{
   GLfloat mat[4];

   glPushMatrix();
   glTranslatef (x, y, z);
   mat[0] = ambr; mat[1] = ambg; mat[2] = ambb; mat[3] = 1.0;
   glMaterialfv (GL_FRONT, GL_AMBIENT, mat);
   mat[0] = difr; mat[1] = difg; mat[2] = difb;
   glMaterialfv (GL_FRONT, GL_DIFFUSE, mat);
   mat[0] = specr; mat[1] = specg; mat[2] = specb;
   glMaterialfv (GL_FRONT, GL_SPECULAR, mat);
   glMaterialf (GL_FRONT, GL_SHININESS, shine*128.0);
   glCallList(teapotList);
   glPopMatrix();
}

void display(void)
{
   int jitter;
   GLint viewport[4];

   glGetIntegerv (GL_VIEWPORT, viewport);
   glClear(GL_ACCUM_BUFFER_BIT);

   for (jitter = 0; jitter < 8; jitter++) {
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      accPerspective (45.0,
         (GLdouble) viewport[2]/(GLdouble) viewport[3],
         1.0, 15.0, 0.0, 0.0,
         0.33*j8[jitter].x, 0.33*j8[jitter].y, 5.0);

/*      ruby, gold, silver, emerald, and cyan teapots   */
      renderTeapot (-1.1, -0.5, -4.5, 0.1745, 0.01175,
                    0.01175, 0.61424, 0.04136, 0.04136,
                    0.727811, 0.626959, 0.626959, 0.6);
      renderTeapot (-0.5, -0.5, -5.0, 0.24725, 0.1995,
                    0.0745, 0.75164, 0.60648, 0.22648,
                    0.628281, 0.555802, 0.366065, 0.4);
      renderTeapot (0.2, -0.5, -5.5, 0.19225, 0.19225,
                    0.19225, 0.50754, 0.50754, 0.50754,
                    0.508273, 0.508273, 0.508273, 0.4);
      renderTeapot (1.0, -0.5, -6.0, 0.0215, 0.1745, 0.0215,
                    0.07568, 0.61424, 0.07568, 0.633,
                    0.727811, 0.633, 0.6);
      renderTeapot (1.8, -0.5, -6.5, 0.0, 0.1, 0.06, 0.0,
                    0.50980392, 0.50980392, 0.50196078,
                    0.50196078, 0.50196078, .25);
      glAccum (GL_ACCUM, 0.125);
   }
   glAccum (GL_RETURN, 1.0);
   glFlush();
}

void reshape(int w, int h)
{
   glViewport(0, 0, (GLsizei) w, (GLsizei) h);
}

/*  Main Loop
 *  Be certain you request an accumulation buffer.
 */
int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB
                        | GLUT_ACCUM | GLUT_DEPTH);
   glutInitWindowSize (400, 400);
   glutInitWindowPosition (100, 100);
   glutCreateWindow (argv[0]);
   init();
   glutReshapeFunc(reshape);
   glutDisplayFunc(display);
   glutMainLoop();
   return 0;
}

Soft Shadows

To accumulate soft shadows due to multiple light sources, render the shadows with one light turned on at a time, and accumulate them together. This can be combined with spatial jittering to antialias the scene at the same time. (See "Shadows" in Chapter 14 for more information about drawing shadows.)

Jittering

If you need to take nine or sixteen samples to antialias an image, you might think that the best choice of points is an equally spaced grid across the pixel. Surprisingly, this is not necessarily true. In fact, sometimes it's a good idea to take points that lie in adjacent pixels. You might want a uniform distribution or a normalized distribution, clustering toward the center of the pixel. (The aforementioned SIGGRAPH paper discusses these issues.) In addition, Table 10-5 shows a few sets of reasonable jittering values to be used for some selected sample counts. Most of the examples in the table are uniformly distributed in the pixel, and all lie within the pixel.

Table 10-5 : (continued) Sample Jittering Values

Count | Values
2 | {0.25, 0.75}, {0.75, 0.25}
3 | {0.5033922635, 0.8317967229}, {0.7806016275, 0.2504380877}, {0.2261828938, 0.4131553612}
4 | {0.375, 0.25}, {0.125, 0.75}, {0.875, 0.25}, {0.625, 0.75}
5 | {0.5, 0.5}, {0.3, 0.1}, {0.7, 0.9}, {0.9, 0.3}, {0.1, 0.7}
6 | {0.4646464646, 0.4646464646}, {0.1313131313, 0.7979797979}, {0.5353535353, 0.8686868686}, {0.8686868686, 0.5353535353}, {0.7979797979, 0.1313131313}, {0.2020202020, 0.2020202020}
8 | {0.5625, 0.4375}, {0.0625, 0.9375}, {0.3125, 0.6875}, {0.6875, 0.8125}, {0.8125, 0.1875}, {0.9375, 0.5625}, {0.4375, 0.0625}, {0.1875, 0.3125}
9 | {0.5, 0.5}, {0.1666666666, 0.9444444444}, {0.5, 0.1666666666}, {0.5, 0.8333333333}, {0.1666666666, 0.2777777777}, {0.8333333333, 0.3888888888}, {0.1666666666, 0.6111111111}, {0.8333333333, 0.7222222222}, {0.8333333333, 0.0555555555}
12 | {0.4166666666, 0.625}, {0.9166666666, 0.875}, {0.25, 0.375}, {0.4166666666, 0.125}, {0.75, 0.125}, {0.0833333333, 0.125}, {0.75, 0.625}, {0.25, 0.875}, {0.5833333333, 0.375}, {0.9166666666, 0.375}, {0.0833333333, 0.625}, {0.583333333, 0.875}
16 | {0.375, 0.4375}, {0.625, 0.0625}, {0.875, 0.1875}, {0.125, 0.0625}, {0.375, 0.6875}, {0.875, 0.4375}, {0.625, 0.5625}, {0.375, 0.9375}, {0.625, 0.3125}, {0.125, 0.5625}, {0.125, 0.8125}, {0.375, 0.1875}, {0.875, 0.9375}, {0.875, 0.6875}, {0.125, 0.3125}, {0.625, 0.8125}


Chapter 11 - OpenGL Programming Guide

OpenGL Programming Guide
Programming Guide > Chapter 11

Chapter 11
Tessellators and Quadrics

Chapter Objectives

After reading this chapter, you'll be able to do the following:

  • Render concave filled polygons by first tessellating them into convex polygons, which can be rendered using standard OpenGL routines.
  • Use the GLU library to create quadrics objects to render and model the surfaces of spheres and cylinders and to tessellate disks (circles) and partial disks (arcs).

The OpenGL library (GL) is designed for low-level operations, both streamlined and accessible to hardware acceleration. The OpenGL Utility Library (GLU) complements the OpenGL library, supporting higher-level operations. Some of the GLU operations are covered in other chapters. Mipmapping (gluBuild*DMipmaps()) and image scaling (gluScaleImage()) are discussed along with other facets of texture mapping in Chapter 9. Several matrix transformation GLU routines (gluOrtho2D(), gluPerspective(), gluLookAt(), gluProject(), and gluUnProject()) are described in Chapter 3. The use of gluPickMatrix() is explained in Chapter 13. The GLU NURBS facilities, which are built atop OpenGL evaluators, are covered in Chapter 12. Only two GLU topics remain: polygon tessellators and quadric surfaces, and those topics are discussed in this chapter.

To optimize performance, the basic OpenGL only renders convex polygons, but the GLU contains routines to tessellate concave polygons into convex ones, which the basic OpenGL can handle. Where the basic OpenGL operates upon simple primitives, such as points, lines, and filled polygons, the GLU can create higher-level objects, such as the surfaces of spheres, cylinders, and cones.

This chapter has the following major sections.

  • "Polygon Tessellation" explains how to tessellate convex polygons into easier-to-render convex polygons.
  • "Quadrics: Rendering Spheres, Cylinders, and Disks" describes how to generate spheres, cylinders, circles and arcs, including data such as surface normals and texture coordinates.

Polygon Tessellation

As discussed in "Describing Points, Lines, and Polygons" in Chapter 2, OpenGL can directly display only simple convex polygons. A polygon is simple if the edges intersect only at vertices, there are no duplicate vertices, and exactly two edges meet at any vertex. If your application requires the display of concave polygons, polygons containing holes, or polygons with intersecting edges, those polygons must first be subdivided into simple convex polygons before they can be displayed. Such subdivision is called tessellation, and the GLU provides a collection of routines that perform tessellation. These routines take as input arbitrary contours, which describe hard-to-render polygons, and they return some combination of triangles, triangle meshes, triangle fans, or lines.

Figure 11-1 shows some contours of polygons that require tessellation: from left to right, a concave polygon, a polygon with a hole, and a self-intersecting polygon.

Figure 11-1 : Contours That Require Tessellation

If you think a polygon may need tessellation, follow these typical steps.

Create a new tessellation object with gluNewTess().

Use gluTessCallback() several times to register callback functions to perform operations during the tessellation. The trickiest case for a callback function is when the tessellation algorithm detects an intersection and must call the function registered for the GLU_TESS_COMBINE callback.

Specify tessellation properties by calling gluTessProperty(). The most important property is the winding rule, which determines the regions that should be filled and those that should remain unshaded.

Create and render tessellated polygons by specifying the contours of one or more closed polygons. If the data for the object is static, encapsulate the tessellated polygons in a display list. (If you don't have to recalculate the tessellation over and over again, using display lists is more efficient.)

If you need to tessellate something else, you may reuse your tessellation object. If you are forever finished with your tessellation object, you may delete it with gluDeleteTess().

Note: The tessellator described here was introduced in version 1.2 of the GLU. If you are using an older version of the GLU, you must use routines described in "Describing GLU Errors". To query which version of GLU you have, use gluGetString(GLU_VERSION), which returns a string with your GLU version number. If you don't seem to have gluGetString() in your GLU, then you have GLU 1.0, which did not yet have the gluGetString() routine.

Create a Tessellation Object

As a complex polygon is being described and tessellated, it has associated data, such as the vertices, edges, and callback functions. All this data is tied to a single tessellation object. To perform tessellation, your program first has to create a tessellation object using the routine gluNewTess().

GLUtesselator* gluNewTess(void);
Creates a new tessellation object and returns a pointer to it. A null pointer is returned if the creation fails.

A single tessellation object can be reused for all your tessellations. This object is required only because library routines might need to do their own tessellations, and they should be able to do so without interfering with any tessellation that your program is doing. It might also be useful to have multiple tessellation objects if you want to use different sets of callbacks for different tessellations. A typical program, however, allocates a single tessellation object and uses it for all its tessellations. There's no real need to free it because it uses a small amount of memory. On the other hand, it never hurts to be tidy.

Tessellation Callback Routines

After you create a tessellation object, you must provide a series of callback routines to be called at appropriate times during the tessellation. After specifying the callbacks, you describe the contours of one or more polygons using GLU routines. When the description of the contours is complete, the tessellation facility invokes your callback routines as necessary.

Any functions that are omitted are simply not called during the tessellation, and any information they might have returned to your program is lost. All are specified by the single routine gluTessCallback().

void gluTessCallback(GLUtesselator *tessobj, GLenum type, void (*fn)());
Associates the callback function fn with the tessellation object tessobj. The type of the callback is determined by the parameter type, which can be GLU_TESS_BEGIN, GLU_TESS_BEGIN_DATA, GLU_TESS_EDGE_FLAG, GLU_TESS_EDGE_FLAG_DATA, GLU_TESS_VERTEX, GLU_TESS_VERTEX_DATA, GLU_TESS_END, GLU_TESS_END_DATA, GLU_TESS_COMBINE, GLU_TESS_COMBINE_DATA, GLU_TESS_ERROR, and GLU_TESS_ERROR_DATA. The twelve possible callback functions have the following prototypes:
GLU_TESS_BEGIN void begin(GLenum type);
GLU_TESS_BEGIN_DATA void begin(GLenum type,
void *user_data);
GLU_TESS_EDGE_FLAG void edgeFlag(GLboolean flag);
GLU_TESS_EDGE_FLAG_DATA void edgeFlag(GLboolean flag,
void *user_data);
GLU_TESS_VERTEX void vertex(void *vertex_data);
GLU_TESS_VERTEX_DATA void vertex(void *vertex_data,
void *user_data);
GLU_TESS_END void end(void);
GLU_TESS_END_DATA void end(void *user_data);
GLU_TESS_ERROR void error(GLenum errno);
GLU_TESS_ERROR_DATA void error(GLenum errno, void *user_data);
GLU_TESS_COMBINE void combine(GLdouble coords[3],
void *vertex_data[4],
GLfloat weight[4],
void **outData);
GLU_TESS_COMBINE_DATA void combine(GLdouble coords[3],
void *vertex_data[4],
GLfloat weight[4],
void **outData,
void *user_data);

To change a callback routine, simply call gluTessCallback() with the new routine. To eliminate a callback routine without replacing it with a new one, pass gluTessCallback() a null pointer for the appropriate function.

As tessellation proceeds, the callback routines are called in a manner
similar to how you use the OpenGL commands glBegin(), glEdgeFlag*(), glVertex*(), and glEnd(). (See "Marking Polygon Boundary Edges" in Chapter 2 for more information about glEdgeFlag*().) The combine callback is used to create new vertices where edges intersect. The error callback is invoked during the tessellation only if something goes wrong.

For every tessellator object created, a GLU_TESS_BEGIN callback is invoked with one of four possible parameters: GL_TRIANGLE_FAN, GL_TRIANGLE_STRIP, GL_TRIANGLES, and GL_LINE_LOOP. When the tessellator decomposes the polygons, the tessellation algorithm will decide which type of triangle primitive is most efficient to use. (If the GLU_TESS_BOUNDARY_ONLY property is enabled, then GL_LINE_LOOP is used for rendering.)

Since edge flags make no sense in a triangle fan or triangle strip, if there is a callback associated with GLU_TESS_EDGE_FLAG that enables edge flags, the GLU_TESS_BEGIN callback is called only with GL_TRIANGLES. The GLU_TESS_EDGE_FLAG callback works exactly analogously to the OpenGL glEdgeFlag*() call.

After the GLU_TESS_BEGIN callback routine is called and before the callback associated with GLU_TESS_END is called, some combination of the GLU_TESS_EDGE_FLAG and GLU_TESS_VERTEX callbacks is invoked (usually by calls to gluTessVertex(), which is described on page 425). The associated edge flags and vertices are interpreted exactly as they are in OpenGL between glBegin() and the matching glEnd().

If something goes wrong, the error callback is passed a GLU error number. A character string describing the error is obtained using the routine gluErrorString(). (See "Describing GLU Errors" for more information about this routine.)

Example 11-1 shows a portion of tess.c, where a tessellation object is created and several callbacks are registered.

Example 11-1 : Registering Tessellation Callbacks: tess.c

/*  a portion of init() */
tobj = gluNewTess();
gluTessCallback(tobj, GLU_TESS_VERTEX,
                   (GLvoid (*) ()) &glVertex3dv);
gluTessCallback(tobj, GLU_TESS_BEGIN,
                   (GLvoid (*) ()) &beginCallback);
gluTessCallback(tobj, GLU_TESS_END,
                   (GLvoid (*) ()) &endCallback);
gluTessCallback(tobj, GLU_TESS_ERROR,
                   (GLvoid (*) ()) &errorCallback);

/*  the callback routines registered by gluTessCallback() */

void beginCallback(GLenum which)
{
   glBegin(which);
}

void endCallback(void)
{
   glEnd();
}

void errorCallback(GLenum errorCode)
{
   const GLubyte *estring;

   estring = gluErrorString(errorCode);
   fprintf (stderr, "Tessellation Error: %s\n", estring);
   exit (0);
}

In Example 11-1, the registered GLU_TESS_VERTEX callback is simply glVertex3dv(), and only the coordinates at each vertex are passed along. However, if you want to specify more information at every vertex, such as a color value, a surface normal vector, or texture coordinate, you'll have to make a more complex callback routine. Example 11-2 shows the start of another tessellated object, further along in program tess.c. The registered function vertexCallback() expects to receive a parameter that is a pointer to six double-length floating point values: the x, y, and z coordinates and the red, green, and blue color values, respectively, for that vertex.

Example 11-2 : Vertex and Combine Callbacks: tess.c

/*  a different portion of init() */
   gluTessCallback(tobj, GLU_TESS_VERTEX,
                   (GLvoid (*) ()) &vertexCallback);
   gluTessCallback(tobj, GLU_TESS_BEGIN,
                   (GLvoid (*) ()) &beginCallback);
   gluTessCallback(tobj, GLU_TESS_END,
                   (GLvoid (*) ()) &endCallback);
   gluTessCallback(tobj, GLU_TESS_ERROR,
                   (GLvoid (*) ()) &errorCallback);
   gluTessCallback(tobj, GLU_TESS_COMBINE,
                   (GLvoid (*) ()) &combineCallback);

/*  new callback routines registered by these calls */
void vertexCallback(GLvoid *vertex)
{
   const GLdouble *pointer;

   pointer = (GLdouble *) vertex;
   glColor3dv(pointer+3);
   glVertex3dv(vertex);
}

void combineCallback(GLdouble coords[3],
                     GLdouble *vertex_data[4],
                     GLfloat weight[4], GLdouble **dataOut )
{
   GLdouble *vertex;
   int i;

   vertex = (GLdouble *) malloc(6 * sizeof(GLdouble));
   vertex[0] = coords[0];
   vertex[1] = coords[1];
   vertex[2] = coords[2];
   for (i = 3; i < 7; i++)
      vertex[i] = weight[0] * vertex_data[0][i]
                  + weight[1] * vertex_data[1][i]
                  + weight[2] * vertex_data[2][i]
                  + weight[3] * vertex_data[3][i];
   *dataOut = vertex;
}

Example 11-2 also shows the use of the GLU_TESS_COMBINE callback. Whenever the tessellation algorithm examines the input contours, detects an intersection, and decides it must create a new vertex, the GLU_TESS_COMBINE callback is invoked. The callback is also called when the tessellator decides to merge features of two vertices that are very close to one another. The newly created vertex is a linear combination of up to four existing vertices, referenced by vertex_data[0..3] in Example 11-2. The coefficients of the linear combination are given by weight[0..3]; these weights sum to 1.0. coords gives the location of the new vertex.

The registered callback routine must allocate memory for another vertex, perform a weighted interpolation of data using vertex_data and weight, and return the new vertex pointer as dataOut. combineCallback() in Example 11-2 interpolates the RGB color value. The function allocates a six-element array, puts the x, y, and z coordinates in the first three elements, and then puts the weighted average of the RGB color values in the last three elements.

User-Specified Data

Six kinds of callbacks can be registered. Since there are two versions of each kind of callback, there are twelve callbacks in all. For each kind of callback, there is one with user-specified data and one without. The user-specified data is given by the application to gluTessBeginPolygon() and is then passed, unaltered, to each *DATA callback routine. With GLU_TESS_BEGIN_DATA, the user-specified data may be used for "per-polygon" data. If you specify both versions of a particular callback, the callback with user_data is used, and the other is ignored. So, although there are twelve callbacks, you can have a maximum of six callback functions active at any time.

For instance, Example 11-2 uses smooth shading, so vertexCallback() specifies an RGB color for every vertex. If you want to do lighting and smooth shading, the callback would specify a surface normal for every vertex. However, if you want lighting and flat shading, you might specify only one surface normal for every polygon, not for every vertex. In that case, you might choose to use the GLU_TESS_BEGIN_DATA callback and pass the vertex coordinates and surface normal in the user_data pointer.

Tessellation Properties

Prior to tessellation and rendering, you may use gluTessProperty() to set several properties to affect the tessellation algorithm. The most important and complicated of these properties is the winding rule, which determines what is considered "interior" and "exterior."

void gluTessProperty(GLUtesselator *tessobj, GLenum property,
GLdouble value);
For the tessellation object tessobj, the current value of property is set to value. property is one of GLU_TESS_BOUNDARY_ONLY, GLU_TESS_TOLERANCE, or GLU_TESS_WINDING_RULE.
If property is GLU_TESS_BOUNDARY_ONLY, value is either GL_TRUE or GL_FALSE. When set to GL_TRUE, polygons are no longer tessellated into filled polygons; line loops are drawn to outline the contours that separate the polygon interior and exterior. The default value is GL_FALSE. (See gluTessNormal() to see how to control the winding direction of the contours.)
If property is GLU_TESS_TOLERANCE, value is a distance used to calculate whether two vertices are close together enough to be merged by the GLU_TESS_COMBINE callback. The tolerance value is multiplied by the largest coordinate magnitude of an input vertex to determine the maximum distance any feature can move as a result of a single merge operation. Feature merging may not be supported by your implementation, and the tolerance value is only a hint. The default tolerance value is zero.
The GLU_TESS_WINDING_RULE property determines which parts of the polygon are on the interior and which are the exterior and should not be filled. value can be one of GLU_TESS_WINDING_ODD (the default), GLU_TESS_WINDING_NONZERO, GLU_TESS_WINDING_POSITIVE, GLU_TESS_WINDING_NEGATIVE, or GLU_TESS_WINDING_ABS_GEQ_TWO.

Winding Numbers and Winding Rules

For a single contour, the winding number of a point is the signed number of revolutions we make around that point while traveling once around the contour (where a counterclockwise revolution is positive and a clockwise revolution is negative). When there are several contours, the individual winding numbers are summed. This procedure associates a signed integer value with each point in the plane. Note that the winding number is the same for all points in a single region.

Figure 11-2 shows three sets of contours and winding numbers for points inside those contours. In the left set, all three contours are counterclockwise, so each nested interior region adds one to the winding number. For the middle set, the two interior contours are drawn clockwise, so the winding number decreases and actually becomes negative.

Figure 11-2 : Winding Numbers for Sample Contours

The winding rule classifies a region as inside if its winding number belongs to the chosen category (odd, nonzero, positive, negative, or "absolute value of greater than or equal to two"). The odd and nonzero rules are common ways to define the interior. The positive, negative, and "absolute value>=2" winding rules have some limited use for polygon CSG (computational solid geometry) operations.

The program tesswind.c demonstrates the effects of winding rules. The four sets of contours shown in Figure 11-3 are rendered. The user can then cycle through the different winding rule properties to see their effects. For each winding rule, the dark areas represent interiors. Note the effect of clockwise and counterclockwise winding.

Figure 11-3 : How Winding Rules Define Interiors

CSG Uses for Winding Rules

GLU_TESS_WINDING_ODD and GLU_TESS_WINDING_NONZERO are the most commonly used winding rules. They work for the most typical cases of shading.

The winding rules are also designed for computational solid geometry (CSG) operations. Thy make it easy to find the union, difference, or intersection (Boolean operations) of several contours.

First, assume that each contour is defined so that the winding number is zero for each exterior region and one for each interior region. (Each contour must not intersect itself.) Under this model, counterclockwise contours define the outer boundary of the polygon, and clockwise contours define holes. Contours may be nested, but a nested contour must be oriented oppositely from the contour that contains it.

If the original polygons do not satisfy this description, they can be converted to this form by first running the tessellator with the GLU_TESS_BOUNDARY_ONLY property turned on. This returns a list of contours satisfying the restriction just described. By creating two tessellator objects, the callbacks from one tessellator can be fed directly as input to the other.

Given two or more polygons of the preceding form, CSG operations can be implemented as follows.

  • UNION - To calculate the union of several contours, draw all input contours as a single polygon. The winding number of each resulting region is the number of original polygons that cover it. The union can be extracted by using the GLU_TESS_WINDING_NONZERO or GLU_TESS_WINDING_POSITIVE winding rules. Note that with the nonzero winding rule, we would get the same result if all contour orientations were reversed.
  • INTERSECTION - This only works for two contours at a time. Draw a single polygon using two contours. Extract the result using GLU_TESS_WINDING_ABS_GEQ_TWO.
  • DIFFERENCE - Suppose you want to compute A diff (B union C union D). Draw a single polygon consisting of the unmodified contours from A, followed by the contours of B, C, and D, with their vertex order reversed. To extract the result, use the GLU_TESS_WINDING_POSITIVE winding rule. (If B, C, and D are the result of a GLU_TESS_BOUNDARY_ONLY operation, an alternative to reversing the vertex order is to use gluTessNormal() to reverse the sign of the supplied normal.

Other Tessellation Property Routines

There are complementary routines, which work alongside gluTessProperty(). gluGetTessProperty() retrieves the current values of tessellator properties. If the tessellator is being used to generate wire frame outlines instead of filled polygons, gluTessNormal() can be used to determine the winding direction of the tessellated polygons.

void gluGetTessProperty(GLUtesselator *tessobj, GLenum property,
GLdouble *value);
For the tessellation object tessobj, the current value of property is returned to value. Values for property and value are the same as for gluTessProperty().
void gluTessNormal(GLUtesselator *tessobj, GLdouble x, GLdouble y,
GLdouble z);
For the tessellation object tessobj, gluTessNormal() defines a normal vector, which controls the winding direction of generated polygons. Before tessellation, all input data is projected into a plane perpendicular to the normal. Then, all output triangles are oriented counterclockwise, with respect to the normal. (Clockwise orientation can be obtained by reversing the sign of the supplied normal.) The default normal is (0, 0, 0).

If you have some knowledge about the location and orientation of the input data, then using gluTessNormal() can increase the speed of the tessellation. For example, if you know that all polygons lie on the x-y plane, call gluTessNormal(tessobj, 0, 0, 1).

The default normal is (0, 0, 0), and its effect is not immediately obvious. In this case, it is expected that the input data lies approximately in a plane, and a plane is fitted to the vertices, no matter how they are truly connected. The sign of the normal is chosen so that the sum of the signed areas of all input contours is nonnegative (where a counterclockwise contour has a positive area). Note that if the input data does not lie approximately in a plane, then projection perpendicular to the computed normal may substantially change the geometry.

Polygon Definition

After all the tessellation properties have been set and the callback actions have been registered, it is finally time to describe the vertices that compromise input contours and tessellate the polygons.

void gluTessBeginPolygon (GLUtesselator *tessobj, void *user_data);
void gluTessEndPolygon (GLUtesselator *tessobj);
Begins and ends the specification of a polygon to be tessellated and associates a tessellation object, tessobj, with it. user_data points to a user-defined data structure, which is passed along all the GLU_TESS_*_DATA callback functions that have been bound.

Calls to gluTessBeginPolygon() and gluTessEndPolygon() surround the definition of one or more contours. When gluTessEndPolygon() is called, the tessellation algorithm is implemented, and the tessellated polygons are generated and rendered. The callback functions and tessellation properties that were bound and set to the tessellation object using gluTessCallback() and gluTessProperty() are used.

void gluTessBeginContour (GLUtesselator *tessobj);
void gluTessEndContour (GLUtesselator *tessobj);
Begins and ends the specification of a closed contour, which is a portion of a polygon. A closed contour consists of zero or more calls to gluTessVertex(), which defines the vertices. The last vertex of each contour is automatically linked to the first.

In practice, a minimum of three vertices is needed for a meaningful contour.

void gluTessVertex (GLUtesselator *tessobj, GLdouble coords[3],
void *vertex_data);
Specifies a vertex in the current contour for the tessellation object. coords contains the three-dimensional vertex coordinates, and vertex_data is a pointer that's sent to the callback associated with GLU_TESS_VERTEX or GLU_TESS_VERTEX_DATA. Typically, vertex_data contains vertex coordinates, surface normals, texture coordinates, color information, or whatever else the application may find useful.

In the program tess.c, a portion of which is shown in Example 11-3, two polygons are defined. One polygon is a rectangular contour with a triangular hole inside, and the other is a smooth-shaded, self-intersecting, five-pointed star. For efficiency, both polygons are stored in display lists. The first polygon consists of two contours; the outer one is wound counterclockwise, and the "hole" is wound clockwise. For the second polygon, the star array contains both the coordinate and color data, and its tessellation callback, vertexCallback(), uses both.

It is important that each vertex is in a different memory location because the vertex data is not copied by gluTessVertex(); only the pointer (vertex_data) is saved. A program that reuses the same memory for several vertices may not get the desired result.

Note: In gluTessVertex(), it may seem redundant to specify the vertex coordinate data twice, for both the coords and vertex_data parameters; however, both are necessary. coords refers only to the vertex coordinates. vertex_data uses the coordinate data, but may also use other information for each vertex.

Example 11-3 : Polygon Definition: tess.c

   GLdouble rect[4][3] = {50.0, 50.0, 0.0,
                          200.0, 50.0, 0.0,
                          200.0, 200.0, 0.0,
                          50.0, 200.0, 0.0};
   GLdouble tri[3][3] = {75.0, 75.0, 0.0,
                         125.0, 175.0, 0.0,
                         175.0, 75.0, 0.0};
   GLdouble star[5][6] = {250.0, 50.0, 0.0, 1.0, 0.0, 1.0,
                          325.0, 200.0, 0.0, 1.0, 1.0, 0.0,
                          400.0, 50.0, 0.0, 0.0, 1.0, 1.0,
                          250.0, 150.0, 0.0, 1.0, 0.0, 0.0,
                          400.0, 150.0, 0.0, 0.0, 1.0, 0.0};

   startList = glGenLists(2);
   tobj = gluNewTess();
   gluTessCallback(tobj, GLU_TESS_VERTEX,
                   (GLvoid (*) ()) &glVertex3dv);
   gluTessCallback(tobj, GLU_TESS_BEGIN,
                   (GLvoid (*) ()) &beginCallback);
   gluTessCallback(tobj, GLU_TESS_END,
                   (GLvoid (*) ()) &endCallback);
   gluTessCallback(tobj, GLU_TESS_ERROR,
                   (GLvoid (*) ()) &errorCallback);

   glNewList(startList, GL_COMPILE);
   glShadeModel(GL_FLAT);
   gluTessBeginPolygon(tobj, NULL);
      gluTessBeginContour(tobj);
         gluTessVertex(tobj, rect[0], rect[0]);
         gluTessVertex(tobj, rect[1], rect[1]);
         gluTessVertex(tobj, rect[2], rect[2]);
         gluTessVertex(tobj, rect[3], rect[3]);
      gluTessEndContour(tobj);
      gluTessBeginContour(tobj);
         gluTessVertex(tobj, tri[0], tri[0]);
         gluTessVertex(tobj, tri[1], tri[1]);
         gluTessVertex(tobj, tri[2], tri[2]);
      gluTessEndContour(tobj);
   gluTessEndPolygon(tobj);
   glEndList();

   gluTessCallback(tobj, GLU_TESS_VERTEX,
                   (GLvoid (*) ()) &vertexCallback);
   gluTessCallback(tobj, GLU_TESS_BEGIN,
                   (GLvoid (*) ()) &beginCallback);
   gluTessCallback(tobj, GLU_TESS_END,
                   (GLvoid (*) ()) &endCallback);
   gluTessCallback(tobj, GLU_TESS_ERROR,
                   (GLvoid (*) ()) &errorCallback);
   gluTessCallback(tobj, GLU_TESS_COMBINE,
                   (GLvoid (*) ()) &combineCallback);

   glNewList(startList + 1, GL_COMPILE);
   glShadeModel(GL_SMOOTH);
   gluTessProperty(tobj, GLU_TESS_WINDING_RULE,
                   GLU_TESS_WINDING_POSITIVE);
   gluTessBeginPolygon(tobj, NULL);
      gluTessBeginContour(tobj);
         gluTessVertex(tobj, star[0], star[0]);
         gluTessVertex(tobj, star[1], star[1]);
         gluTessVertex(tobj, star[2], star[2]);
         gluTessVertex(tobj, star[3], star[3]);
         gluTessVertex(tobj, star[4], star[4]);
      gluTessEndContour(tobj);
   gluTessEndPolygon(tobj);
   glEndList();

Deleting a Tessellator Object

If you no longer need a tessellation object, you can delete it and free all associated memory with gluDeleteTess().

void gluDeleteTess(GLUtesselator *tessobj);
Deletes the specified tessellation object, tessobj, and frees all associated memory.

Tessellator Performance Tips

For best performance, remember these rules.

Cache the output of the tessellator in a display list or other user structure. To obtain the post-tessellation vertex coordinates, tessellate the polygons while in feedback mode. (See "Feedback" in Chapter 13.)

Use gluTessNormal() to supply the polygon normal.

Use the same tessellator object to render many polygons rather than allocate a new tessellator for each one. (In a multithreaded, multiprocessor environment, you may get better performance using several tessellators.)

Describing GLU Errors

The GLU provides a routine for obtaining a descriptive string for an error code. This routine is not limited to tessellation but is also used for NURBS and quadrics errors, as well as errors in the base GL. (See "Error Handling" in Chapter 14 for information about OpenGL's error handling facility.)

Backward Compatibility

If you are using the 1.0 or 1.1 version of GLU, you have a much less powerful tessellator available. The 1.0/1.1 tessellator handles only simple nonconvex polygons or simple polygons containing holes. It does not properly tessellate intersecting contours (no COMBINE callback), nor process per-polygon data.

The 1.0/1.1 tessellator has some similarities to the current tessellator. gluNewTess() and gluDeleteTess() are used for both tessellators. The main vertex specification routine remains gluTessVertex(). The callback mechanism is controlled by gluTessCallback(), although there are only five callback functions that can be registered, a subset of the current twelve.

Here are the prototypes for the 1.0/1.1 tessellator. The 1.0/1.1 tessellator still works in GLU 1.2, but its use is no longer recommended.

void gluBeginPolygon(GLUtriangulatorObj *tessobj);
void gluNextContour(GLUtriangulatorObj *tessobj, GLenum type);
void gluEndPolygon(GLUtriangulatorObj *tessobj);
The outermost contour must be specified first, and it does not require an initial call to gluNextContour(). For polygons without holes, only one contour is defined, and gluNextContour() is not used. If a polygon has multiple contours (that is, holes or holes within holes), the contours are specified one after the other, each preceded by gluNextContour(). gluTessVertex() is called for each vertex of a contour.
For gluNextContour(), type can be GLU_EXTERIOR, GLU_INTERIOR, GLU_CCW, GLU_CW, or GLU_UNKNOWN. These serve only as hints to the tessellation. If you get them right, the tessellation might go faster. If you get them wrong, they're ignored, and the tessellation still works. For polygons with holes, one contour is the exterior contour and the other's interior. The first contour is assumed to be of type GLU_EXTERIOR. Choosing clockwise and counterclockwise orientation is arbitrary in three dimensions; however, there are two different orientations in any plane, and the GLU_CCW and GLU_CW types should be used consistently. Use GLU_UNKNOWN if you don't have a clue.

It is highly recommended that you convert GLU 1.0/1.1 code to the new tessellation interface for GLU 1.2 by following these steps.

Change references to the major data structure type from GLUtriangulatorObj to GLUtesselator. In GLU 1.2, GLUtriangulatorObj and GLUtesselator are defined to be the same type.

Convert gluBeginPolygon() to two commands: gluTessBeginPolygon() and gluTessBeginContour(). All contours must be explicitly started, including the first one.

Convert gluNextContour() to both gluTessEndContour() and gluTessBeginContour(). You have to end the previous contour before starting the next one.

Convert gluEndPolygon() to both gluTessEndContour() and gluTessEndPolygon(). The final contour must be closed.

Change references to constants to gluTessCallback(). In GLU 1.2, GLU_BEGIN, GLU_VERTEX, GLU_END, GLU_ERROR, and GLU_EDGE_FLAG are defined as synonyms for GLU_TESS_BEGIN, GLU_TESS_VERTEX, GLU_TESS_END, GLU_TESS_ERROR, and GLU_TESS_EDGE_FLAG.

Quadrics: Rendering Spheres, Cylinders, and Disks

The base OpenGL library only provides support for modeling and rendering simple points, lines, and convex filled polygons. Neither 3D objects, nor commonly used 2D objects such as circles, are directly available.

Throughout this book, you've been using GLUT to create some 3D objects. The GLU also provides routines to model and render tessellated, polygonal approximations for a variety of 2D and 3D shapes (spheres, cylinders, disks, and parts of disks), which can be calculated with quadric equations. This includes routines to draw the quadric surfaces in a variety of styles and orientations. Quadric surfaces are defined by the following general quadratic equation:

a1x2 + a2y2 + a3z2 + a4xy + a5yx + a6xz + a7x + a8y + a9z + a10 = 0

(See David Rogers' Procedural Elements for Computer Graphics. New York, NY: McGraw-Hill Book Company, 1985.) Creating and rendering a quadric surface is similar to using the tessellator. To use a quadrics object, follow these steps.

To create a quadrics object, use gluNewQuadric().

Specify the rendering attributes for the quadrics object (unless you're satisfied with the default values).

Use gluQuadricOrientation() to control the winding direction and differentiate the interior from the exterior.

Use gluQuadricDrawStyle() to choose between rendering the object as points, lines, or filled polygons.

For lit quadrics objects, use gluQuadricNormals() to specify one normal per vertex or one normal per face. The default is that no normals are generated at all.

For textured quadrics objects, use gluQuadricTexture() if you want to generate texture coordinates.

Prepare for problems by registering an error-handling routine with gluQuadricCallback(). Then, if an error occurs during rendering, the routine you've specified is invoked.

Now invoke the rendering routine for the desired type of quadrics object: gluSphere(), gluCylinder(), gluDisk(), or gluPartialDisk(). For best performance for static data, encapsulate the quadrics object in a display list.

When you're completely finished with it, destroy this object with gluDeleteQuadric(). If you need to create another quadric, it's best to reuse your quadrics object.

Manage Quadrics Objects

A quadrics object consists of parameters, attributes, and callbacks that are stored in a data structure of type GLUquadricObj. A quadrics object may generate vertices, normals, texture coordinates, and other data, all of which may be used immediately or stored in a display list for later use. The following routines create, destroy, and report upon errors of a quadrics object.

GLUquadricObj* gluNewQuadric (void);
Creates a new quadrics object and returns a pointer to it. A null pointer is returned if the routine fails.
void gluDeleteQuadric (GLUquadricObj *qobj);
Destroys the quadrics object qobj and frees up any memory used by it.
void gluQuadricCallback (GLUquadricObj *qobj, GLenum which, void (*fn)());
Defines a function fn to be called in special circumstances. GLU_ERROR is the only legal value for which, so fn is called when an error occurs. If fn is NULL, any existing callback is erased.

For GLU_ERROR, fn is called with one parameter, which is the error code. gluErrorString() can be used to convert the error code into an ASCII string.

Control Quadrics Attributes

The following routines affect the kinds of data generated by the quadrics routines. Use these routines before you actually specify the primitives.

Example 11-4, quadric.c, on page 435, demonstrates changing the drawing style and the kind of normals generated as well as creating quadrics objects, error handling, and drawing the primitives.

void gluQuadricDrawStyle (GLUquadricObj *qobj, GLenum drawStyle);
For the quadrics object qobj, drawStyle controls the rendering style. Legal values for drawStyle are GLU_POINT, GLU_LINE, GLU_SILHOUETTE, and GLU_FILL.

GLU_POINT and GLU_LINE specify that primitives should be rendered as a point at every vertex or a line between each pair of connected vertices.

GLU_SILHOUETTE specifies that primitives are rendered as lines, except that edges separating coplanar faces are not drawn. This is most often used for gluDisk() and gluPartialDisk().

GLU_FILL specifies rendering by filled polygons, where the polygons are drawn in a counterclockwise fashion with respect to their normals. This may be affected by gluQuadricOrientation().

void gluQuadricOrientation (GLUquadricObj *qobj, GLenum orientation);
For the quadrics object qobj, orientation is either GLU_OUTSIDE (the default) or GLU_INSIDE, which controls the direction in which normals are pointing.

For gluSphere() and gluCylinder(), the definitions of outside and inside are obvious. For gluDisk() and gluPartialDisk(), the positive z side of the disk is considered to be outside.

void gluQuadricNormals (GLUquadricObj *qobj, GLenum normals);
For the quadrics object qobj, normals is one of GLU_NONE (the default), GLU_FLAT, or GLU_SMOOTH.

gluQuadricNormals() is used to specify when to generate normal vectors. GLU_NONE means that no normals are generated and is intended for use without lighting. GLU_FLAT generates one normal for each facet, which is often best for lighting with flat shading. GLU_SMOOTH generates one normal for every vertex of the quadric, which is usually best for lighting with smooth shading.

void gluQuadricTexture (GLUquadricObj *qobj,
GLboolean textureCoords);
For the quadrics object qobj, textureCoords is either GL_FALSE (the default) or GL_TRUE. If the value of textureCoords is GL_TRUE, then texture coordinates are generated for the quadrics object. The manner in which the texture coordinates are generated varies, depending upon the type of quadrics object rendered.

Quadrics Primitives

The following routines actually generate the vertices and other data that constitute a quadrics object. In each case, qobj refers to a quadrics object created by gluNewQuadric().

void gluSphere (GLUquadricObj *qobj, GLdouble radius,
GLint slices, GLint stacks);
Draws a sphere of the given radius, centered around the origin, (0, 0, 0). The sphere is subdivided around the z axis into a number of slices (similar to longitude) and along the z axis into a number of stacks (latitude).
If texture coordinates are also generated by the quadrics facility, the t coordinate ranges from 0.0 at z = -radius to 1.0 at z = radius, with t increasing linearly along longitudinal lines. Meanwhile, s ranges from 0.0 at the +y axis, to 0.25 at the +x axis, to 0.5 at the -y axis, to 0.75 at the -x axis, and back to 1.0 at the +y axis.
void gluCylinder (GLUquadricObj *qobj, GLdouble baseRadius,
GLdouble topRadius, GLdouble height,
GLint slices, GLint stacks);
Draws a cylinder oriented along the z axis, with the base of the cylinder at z = 0 and the top at z = height. Like a sphere, the cylinder is subdivided around the z axis into a number of slices and along the z axis into a number of stacks. baseRadius is the radius of the cylinder at z = 0. topRadius is the radius of the cylinder at z = height. If topRadius is set to zero, then a cone is generated.
If texture coordinates are generated by the quadrics facility, then the t coordinate ranges linearly from 0.0 at z = 0 to 1.0 at z = height. The s texture coordinates are generated the same way as they are for a sphere.

Note: The cylinder is not closed at the top or bottom. The disks at the base and at the top are not drawn.

void gluDisk (GLUquadricObj *qobj, GLdouble innerRadius,
GLdouble outerRadius, GLint slices, GLint rings);
Draws a disk on the z = 0 plane, with a radius of outerRadius and a concentric circular hole with a radius of innerRadius. If innerRadius is 0, then no hole is created. The disk is subdivided around the z axis into a number of slices (like slices of pizza) and also about the z axis into a number of concentric rings.
With respect to orientation, the +z side of the disk is considered to be "outside"; that is, any normals generated point along the +z axis. Otherwise, the normals point along the -z axis.
If texture coordinates are generated by the quadrics facility, then the texture coordinates are generated linearly such that where R=outerRadius, the values for s and t at (R, 0, 0) is (1, 0.5), at (0, R, 0) they are (0.5, 1), at (-R, 0, 0) they are (0, 0.5), and at (0, -R, 0) they are (0.5, 0).
void gluPartialDisk (GLUquadricObj *qobj, GLdouble innerRadius,
GLdouble outerRadius, GLint slices, GLint rings,
GLdouble startAngle, GLdouble sweepAngle);
Draws a partial disk on the z = 0 plane. A partial disk is similar to a complete disk, in terms of outerRadius, innerRadius, slices, and rings. The difference is that only a portion of a partial disk is drawn, starting from startAngle through startAngle+sweepAngle (where startAngle and sweepAngle are measured in degrees, where 0 degrees is along the +y axis, 90 degrees along the +x axis, 180 along the -y axis, and 270 along the -x axis).
A partial disk handles orientation and texture coordinates in the same way as a complete disk.

Note: For all quadrics objects, it's better to use the *Radius, height, and similar arguments to scale them rather than the glScale*() command so that the unit-length normals that are generated don't have to be renormalized. Set the rings and stacks arguments to values other than one to force lighting calculations at a finer granularity, especially if the material specularity is high.

Example 11-4 shows each of the quadrics primitives being drawn, as well as the effects of different drawing styles.

Example 11-4 : Quadrics Objects: quadric.c

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <stdio.h>
#include <stdlib.h>

GLuint startList;

void errorCallback(GLenum errorCode)
{
   const GLubyte *estring;

   estring = gluErrorString(errorCode);
   fprintf(stderr, "Quadric Error: %s\n", estring);
   exit(0);
}

void init(void)
{
   GLUquadricObj *qobj;
   GLfloat mat_ambient[] = { 0.5, 0.5, 0.5, 1.0 };
   GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
   GLfloat mat_shininess[] = { 50.0 };
   GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };
   GLfloat model_ambient[] = { 0.5, 0.5, 0.5, 1.0 };

   glClearColor(0.0, 0.0, 0.0, 0.0);

   glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
   glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
   glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
   glLightfv(GL_LIGHT0, GL_POSITION, light_position);
   glLightModelfv(GL_LIGHT_MODEL_AMBIENT, model_ambient);

   glEnable(GL_LIGHTING);
   glEnable(GL_LIGHT0);
   glEnable(GL_DEPTH_TEST);

/* Create 4 display lists, each with a different quadric object.
 * Different drawing styles and surface normal specifications
 * are demonstrated.
 */
   startList = glGenLists(4);
   qobj = gluNewQuadric();
   gluQuadricCallback(qobj, GLU_ERROR, errorCallback);

   gluQuadricDrawStyle(qobj, GLU_FILL); /* smooth shaded */
   gluQuadricNormals(qobj, GLU_SMOOTH);
   glNewList(startList, GL_COMPILE);
      gluSphere(qobj, 0.75, 15, 10);
   glEndList();

   gluQuadricDrawStyle(qobj, GLU_FILL); /* flat shaded */
   gluQuadricNormals(qobj, GLU_FLAT);
   glNewList(startList+1, GL_COMPILE);
      gluCylinder(qobj, 0.5, 0.3, 1.0, 15, 5);
   glEndList();

   gluQuadricDrawStyle(qobj, GLU_LINE); /* wireframe */
   gluQuadricNormals(qobj, GLU_NONE);
   glNewList(startList+2, GL_COMPILE);
      gluDisk(qobj, 0.25, 1.0, 20, 4);
   glEndList();

   gluQuadricDrawStyle(qobj, GLU_SILHOUETTE);
   gluQuadricNormals(qobj, GLU_NONE);
   glNewList(startList+3, GL_COMPILE);
      gluPartialDisk(qobj, 0.0, 1.0, 20, 4, 0.0, 225.0);
   glEndList();
}

void display(void)
{
   glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   glPushMatrix();

   glEnable(GL_LIGHTING);
   glShadeModel (GL_SMOOTH);
   glTranslatef(-1.0, -1.0, 0.0);
   glCallList(startList);

   glShadeModel (GL_FLAT);
   glTranslatef(0.0, 2.0, 0.0);
   glPushMatrix();
   glRotatef(300.0, 1.0, 0.0, 0.0);
   glCallList(startList+1);
   glPopMatrix();

   glDisable(GL_LIGHTING);
   glColor3f(0.0, 1.0, 1.0);
   glTranslatef(2.0, -2.0, 0.0);
   glCallList(startList+2);

   glColor3f(1.0, 1.0, 0.0);
   glTranslatef(0.0, 2.0, 0.0);
   glCallList(startList+3);

   glPopMatrix();
   glFlush();
}

void reshape (int w, int h)
{
   glViewport(0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   if (w <= h)
      glOrtho(-2.5, 2.5, -2.5*(GLfloat)h/(GLfloat)w,
         2.5*(GLfloat)h/(GLfloat)w, -10.0, 10.0);
   else
      glOrtho(-2.5*(GLfloat)w/(GLfloat)h,
         2.5*(GLfloat)w/(GLfloat)h, -2.5, 2.5, -10.0, 10.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
}

void keyboard(unsigned char key, int x, int y)
{
   switch (key) {
      case 27:
         exit(0);
         break;
   }
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);
   glutInitWindowSize(500, 500);
   glutInitWindowPosition(100, 100);
   glutCreateWindow(argv[0]);
   init();
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyboard);
   glutMainLoop();
   return 0;
}


Chapter 12 - OpenGL Programming Guide

OpenGL Programming Guide
Programming Guide > Chapter 12

Chapter 12
Evaluators and NURBS

Chapter Objectives

Advanced

After reading this chapter, you'll be able to do the following:

  • Use OpenGL evaluator commands to draw basic curves and surfaces
  • Use the GLU's higher-level NURBS facility to draw more complex curves and surfaces

Note that this chapter presumes a number of prerequisites; they're listed in "Prerequisites."

At the lowest level, graphics hardware draws points, line segments, and polygons, which are usually triangles and quadrilaterals. Smooth curves and surfaces are drawn by approximating them with large numbers of small line segments or polygons. However, many useful curves and surfaces can be described mathematically by a small number of parameters such as a few control points. Saving the 16 control points for a surface requires much less storage than saving 1000 triangles together with the normal vector information at each vertex. In addition, the 1000 triangles only approximate the true surface, but the control points accurately describe the real surface.

Evaluators provide a way to specify points on a curve or surface (or part of one) using only the control points. The curve or surface can then be rendered at any precision. In addition, normal vectors can be calculated for surfaces automatically. You can use the points generated by an evaluator in many ways - to draw dots where the surface would be, to draw a wireframe version of the surface, or to draw a fully lighted, shaded, and even textured version.

You can use evaluators to describe any polynomial or rational polynomial splines or surfaces of any degree. These include almost all splines and spline surfaces in use today, including B-splines, NURBS (Non-Uniform Rational B-Spline) surfaces, Bezier curves and surfaces, and Hermite splines. Since evaluators provide only a low-level description of the points on a curve or surface, they're typically used underneath utility libraries that provide a higher-level interface to the programmer. The GLU's NURBS facility is such a higher-level interface - the NURBS routines encapsulate lots of complicated code. Much of the final rendering is done with evaluators, but for some conditions (trimming curves, for example) the NURBS routines use planar polygons for rendering.

This chapter contains the following major sections.

  • "Prerequisites" discusses what knowledge is assumed for this chapter. It also gives several references where you can obtain this information.
  • "Evaluators" explains how evaluators work and how to control them using the appropriate OpenGL commands.
  • "The GLU NURBS Interface" describes the GLU routines for creating NURBS surfaces.

Prerequisites

Evaluators make splines and surfaces that are based on a Bezier (or Bernstein) basis. The defining formulas for the functions in this basis are given in this chapter, but the discussion doesn't include derivations or even lists of all their interesting mathematical properties. If you want to use evaluators to draw curves and surfaces using other bases, you must know how to convert your basis to a Bezier basis. In addition, when you render a Bezier surface or part of it using evaluators, you need to determine the granularity of your subdivision. Your decision needs to take into account the trade-off between high-quality (highly subdivided) images and high speed. Determining an appropriate subdivision strategy can be quite complicated - too complicated to be discussed here.

Similarly, a complete discussion of NURBS is beyond the scope of this book. The GLU NURBS interface is documented here, and programming examples are provided for readers who already understand the subject. In what follows, you already should know about NURBS control points, knot sequences, and trimming curves.

If you lack some of these prerequisites, the following references will help.

  • Farin, Gerald E., Curves and Surfaces for Computer-Aided Geometric Design, Fourth Edition. San Diego, CA: Academic Press, 1996.
  • Farin, Gerald E., NURB Curves and Surfaces: from Projective Geometry to Practical Use. Wellesley, MA: A. K. Peters Ltd., 1995.
  • Farin, Gerald E., editor, NURBS for Curve and Surface Design, Society for Industrial and Applied Mathematics, Philadelphia, PA, 1991.
  • Hoschek, Josef and Dieter Lasser, Fundamentals of Computer Aided Geometric Design. Wellesley, MA: A. K. Peters Ltd., 1993.
  • Piegl, Les and Wayne Tiller, The NURBS Book. New York, NY: Springer-Verlag, 1995.

Note: Some terms used in this chapter might have slightly different meanings in other books on spline curves and surfaces, since there isn't total agreement among the practitioners of this art. Generally, the OpenGL meanings are a bit more restrictive. For example, OpenGL evaluators always use Bezier bases; in other contexts, evaluators might refer to the same concept, but with an arbitrary basis.

Evaluators

A Bezier curve is a vector-valued function of one variable

C(u) = [X(u) Y(u) Z(u)]

where u varies in some domain (say [0,1]). A Bezier surface patch is a vector-valued function of two variables

S(u,v) = [X(u,v) Y(u,v) Z(u,v)]

where u and v can both vary in some domain. The range isn't necessarily three-dimensional as shown here. You might want two-dimensional output for curves on a plane or texture coordinates, or you might want four-dimensional output to specify RGBA information. Even one-dimensional output may make sense for gray levels.

For each u (or u and v, in the case of a surface), the formula for C() (or S()) calculates a point on the curve (or surface). To use an evaluator, first define the function C() or S(), enable it, and then use the glEvalCoord1() or glEvalCoord2() command instead of glVertex*(). This way, the curve or surface vertices can be used like any other vertices - to form points or lines, for example. In addition, other commands automatically generate series of vertices that produce a regular mesh uniformly spaced in u (or in u and v). One- and two-dimensional evaluators are similar, but the description is somewhat simpler in one dimension, so that case is discussed first.

One-Dimensional Evaluators

This section presents an example of using one-dimensional evaluators to draw a curve. It then describes the commands and equations that control evaluators.

One-Dimensional Example: A Simple Bezier Curve

The program shown in Example 12-1 draws a cubic Bezier curve using four control points, as shown in Figure 12-1.

Figure 12-1 : Bezier Curve

Example 12-1 : Bezier Curve with Four Control Points: bezcurve.c

#include <GL/gl.h>
#include <GL/glu.h>
#include <stdlib.h>
#include <GL/glut.h>

GLfloat ctrlpoints[4][3] = {
        { -4.0, -4.0, 0.0}, { -2.0, 4.0, 0.0},
        {2.0, -4.0, 0.0}, {4.0, 4.0, 0.0}};

void init(void)
{
   glClearColor(0.0, 0.0, 0.0, 0.0);
   glShadeModel(GL_FLAT);
   glMap1f(GL_MAP1_VERTEX_3, 0.0, 1.0, 3, 4, &ctrlpoints[0][0]);
   glEnable(GL_MAP1_VERTEX_3);
}

void display(void)
{
   int i;

   glClear(GL_COLOR_BUFFER_BIT);
   glColor3f(1.0, 1.0, 1.0);
   glBegin(GL_LINE_STRIP);
      for (i = 0; i <= 30; i++)
         glEvalCoord1f((GLfloat) i/30.0);
   glEnd();
   /* The following code displays the control points as dots. */
   glPointSize(5.0);
   glColor3f(1.0, 1.0, 0.0);
   glBegin(GL_POINTS);
      for (i = 0; i < 4; i++)
         glVertex3fv(&ctrlpoints[i][0]);
   glEnd();
   glFlush();
}

void reshape(int w, int h)
{
   glViewport(0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   if (w <= h)
      glOrtho(-5.0, 5.0, -5.0*(GLfloat)h/(GLfloat)w,
               5.0*(GLfloat)h/(GLfloat)w, -5.0, 5.0);
   else
      glOrtho(-5.0*(GLfloat)w/(GLfloat)h,
               5.0*(GLfloat)w/(GLfloat)h, -5.0, 5.0, -5.0, 5.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
   glutInitWindowSize (500, 500);
   glutInitWindowPosition (100, 100);
   glutCreateWindow (argv[0]);
   init ();
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutMainLoop();
   return 0;
}

A cubic Bezier curve is described by four control points, which appear in this example in the ctrlpoints[][] array. This array is one of the arguments to glMap1f(). All the arguments for this command are as follows:

GL_MAP1_VERTEX_3 Three-dimensional control points are provided and three-dimensional vertices are produced
0.0 Low value of parameter u
1.0 High value of parameter u
3 The number of floating-point values to advance in the data between one control point and the next
4 The order of the spline, which is the degree+1: in this case, the degree is 3 (since this is a cubic curve)
&ctrlpoints[0][0] Pointer to the first control point's data

Note that the second and third arguments control the parameterization of the curve - as the variable u ranges from 0.0 to 1.0, the curve goes from one end to the other. The call to glEnable() enables the one-dimensional evaluator for three-dimensional vertices.

The curve is drawn in the routine display() between the glBegin() and glEnd() calls. Since the evaluator is enabled, the command glEvalCoord1f() is just like issuing a glVertex() command with the coordinates of a vertex on the curve corresponding to the input parameter u.

Defining and Evaluating a One-Dimensional Evaluator

The Bernstein polynomial of degree n (or order n+1) is given by

If Pi represents a set of control points (one-, two-, three-, or even four- dimensional), then the equation

represents a Bezier curve as u varies from 0.0 to 1.0. To represent the same curve but allowing u to vary between u1 and u2 instead of 0.0 and 1.0, evaluate

The command glMap1() defines a one-dimensional evaluator that uses these equations.

void glMap1{fd}(GLenum target, TYPEu1, TYPE u2, GLint stride,
GLint order, const TYPE*points);
Defines a one-dimensional evaluator. The target parameter specifies what the control points represent, as shown in Table 12-1, and therefore how many values need to be supplied in points. The points can represent vertices, RGBA color data, normal vectors, or texture coordinates. For
example, with GL_MAP1_COLOR_4, the evaluator generates color data along a curve in four-dimensional (RGBA) color space. You also use the parameter values listed in Table 12-1 to enable each defined evaluator before you invoke it. Pass the appropriate value to glEnable() or glDisable() to enable or disable the evaluator.
The second two parameters for glMap1*(), u1 and u2, indicate the range for the variable u. The variable stride is the number of single- or double-precision values (as appropriate) in each block of storage. Thus, it's an offset value between the beginning of one control point and the beginning of the next.
The order is the degree plus one, and it should agree with the number of control points. The points parameter points to the first coordinate of the first control point. Using the example data structure for glMap1*(), use the following for points:
(GLfloat *)(&ctlpoints[0].x)

Table 12-1 : Types of Control Points for glMap1*()
Parameter | Meaning
GL_MAP1_VERTEX_3 | x, y, z vertex coordinates
GL_MAP1_VERTEX_4 | x, y, z, w vertex coordinates
GL_MAP1_INDEX | color index
GL_MAP1_COLOR_4 | R, G, B, A
GL_MAP1_NORMAL | normal coordinates
GL_MAP1_TEXTURE_COORD_1 | s texture coordinates
GL_MAP1_TEXTURE_COORD_2 | s, t texture coordinates
GL_MAP1_TEXTURE_COORD_3 | s, t, r texture coordinates
GL_MAP1_TEXTURE_COORD_4 | s, t, r, q texture coordinates

More than one evaluator can be evaluated at a time. If you have both a GL_MAP1_VERTEX_3 and a GL_MAP1_COLOR_4 evaluator defined and enabled, for example, then calls to glEvalCoord1() generate both a position and a color. Only one of the vertex evaluators can be enabled at a time, although you might have defined both of them. Similarly, only one of the texture evaluators can be active. Other than that, however, evaluators can be used to generate any combination of vertex, normal, color, and texture-coordinate data. If more than one evaluator of the same type is defined and enabled, the one of highest dimension is used.

Use glEvalCoord1*() to evaluate a defined and enabled one-dimensional map.

void glEvalCoord1{fd}(TYPE u);
void glEvalCoord1{fd}v(TYPE *u);
Causes evaluation of the enabled one-dimensional maps. The argument u is the value (or a pointer to the value, in the vector version of the command) of the domain coordinate.

For evaluated vertices, values for color, color index, normal vectors, and texture coordinates are generated by evaluation. Calls to glEvalCoord*() do not use the current values for color, color index, normal vectors, and texture coordinates. glEvalCoord*() also leaves those values unchanged.

Defining Evenly Spaced Coordinate Values in One Dimension

You can use glEvalCoord1() with any values for u, but by far the most common use is with evenly spaced values, as shown previously in Example 12-1. To obtain evenly spaced values, define a one-dimensional grid using glMapGrid1*() and then apply it using glEvalMesh1().

void glMapGrid1{fd}(GLint n, TYPEu1, TYPEu2);
Defines a grid that goes from u1 to u2 in n steps, which are evenly spaced.
void glEvalMesh1(GLenum mode, GLint p1, GLint p2);
Applies the currently defined map grid to all enabled evaluators. The mode can be either GL_POINT or GL_LINE, depending on whether you want to draw points or a connected line along the curve. The call has exactly the same effect as issuing a glEvalCoord1() for each of the steps between and including p1 and p2, where 0 <= p1, p2 <= n. Programmatically, it's equivalent to the following:
glBegin(GL_POINTS);    /* OR glBegin(GL_LINE_STRIP); */
 for (i = p1; i <= p2; i++)

    glEvalCoord1(u1 + i*(u2-u1)/n);

glEnd();
except that if i = 0 or i = n, then glEvalCoord1() is called with exactly u1 or u2 as its parameter.

Two-Dimensional Evaluators

In two dimensions, everything is similar to the one-dimensional case, except that all the commands must take two parameters, u and v, into account. Points, colors, normals, or texture coordinates must be supplied over a surface instead of a curve. Mathematically, the definition of a Bezier surface patch is given by

where Pij are a set of m*n control points, and the Bi are the same Bernstein polynomials for one dimension. As before, the Pij can represent vertices, normals, colors, or texture coordinates.

The procedure to use two-dimensional evaluators is similar to the procedure for one dimension.

Define the evaluator(s) with glMap2*().

Enable them by passing the appropriate value to glEnable().

Invoke them either by calling glEvalCoord2() between a glBegin() and glEnd() pair or by specifying and then applying a mesh with glMapGrid2() and glEvalMesh2().

Defining and Evaluating a Two-Dimensional Evaluator

Use glMap2*() and glEvalCoord2*() to define and then invoke a two-dimensional evaluator.

void glMap2{fd}(GLenum target, TYPEu1, TYPEu2, GLint ustride,
GLint uorder, TYPEv1, TYPEv2, GLint vstride,
GLint vorder, TYPE points);
The target parameter can have any of the values in Table 12-1, except that the string MAP1 is replaced with MAP2. As before, these values are also used with glEnable() to enable the corresponding evaluator. Minimum and maximum values for both u and v are provided as u1, u2, v1, and v2. The parameters ustride and vstride indicate the number of single- or double-precision values (as appropriate) between independent settings for these values, allowing users to select a subrectangle of control points out of a much larger array. For example, if the data appears in the form
GLfloat ctlpoints[100][100][3];

and you want to use the 4x4 subset beginning at ctlpoints[20][30], choose ustride to be 100*3 and vstride to be 3. The starting point, points, should be set to &ctlpoints[20][30][0]. Finally, the order parameters, uorder and vorder, can be different, allowing patches that are cubic in one direction and quadratic in the other, for example.
void glEvalCoord2{fd}(TYPE u, TYPE v);
void glEvalCoord2{fd}v(TYPE *values);
Causes evaluation of the enabled two-dimensional maps. The arguments u and v are the values (or a pointer to the values u and v, in the vector version of the command) for the domain coordinates. If either of the vertex evaluators is enabled (GL_MAP2_VERTEX_3 or GL_MAP2_VERTEX_4), then the normal to the surface is computed analytically. This normal is associated with the generated vertex if automatic normal generation has been enabled by passing GL_AUTO_NORMAL to glEnable(). If it's disabled, the corresponding enabled normal map is used to produce a normal. If no such map exists, the current normal is used.

Two-Dimensional Example: A Bezier Surface

Example 12-2 draws a wireframe Bezier surface using evaluators, as shown in Figure 12-2. In this example, the surface is drawn with nine curved lines in each direction. Each curve is drawn as 30 segments. To get the whole program, add the reshape() and main() routines from Example 12-1.

Figure 12-2 : Bezier Surface

Example 12-2 : Bezier Surface: bezsurf.c

#include <GL/gl.h>
#include <GL/glu.h>
#include <stdlib.h>
#include <GL/glut.h>

GLfloat ctrlpoints[4][4][3] = {
   {{-1.5, -1.5, 4.0}, {-0.5, -1.5, 2.0},
    {0.5, -1.5, -1.0}, {1.5, -1.5, 2.0}},
   {{-1.5, -0.5, 1.0}, {-0.5, -0.5, 3.0},
    {0.5, -0.5, 0.0}, {1.5, -0.5, -1.0}},
   {{-1.5, 0.5, 4.0}, {-0.5, 0.5, 0.0},
    {0.5, 0.5, 3.0}, {1.5, 0.5, 4.0}},
   {{-1.5, 1.5, -2.0}, {-0.5, 1.5, -2.0},
    {0.5, 1.5, 0.0}, {1.5, 1.5, -1.0}}
};

void display(void)
{
   int i, j;

   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   glColor3f(1.0, 1.0, 1.0);
   glPushMatrix ();
   glRotatef(85.0, 1.0, 1.0, 1.0);
   for (j = 0; j <= 8; j++) {
      glBegin(GL_LINE_STRIP);
      for (i = 0; i <= 30; i++)
         glEvalCoord2f((GLfloat)i/30.0, (GLfloat)j/8.0);
      glEnd();
      glBegin(GL_LINE_STRIP);
      for (i = 0; i <= 30; i++)
         glEvalCoord2f((GLfloat)j/8.0, (GLfloat)i/30.0);
      glEnd();
   }
   glPopMatrix ();
   glFlush();
}

void init(void)
{
   glClearColor (0.0, 0.0, 0.0, 0.0);
   glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4,
           0, 1, 12, 4, &ctrlpoints[0][0][0]);
   glEnable(GL_MAP2_VERTEX_3);
   glMapGrid2f(20, 0.0, 1.0, 20, 0.0, 1.0);
   glEnable(GL_DEPTH_TEST);
   glShadeModel(GL_FLAT);
}

Defining Evenly Spaced Coordinate Values in Two Dimensions

In two dimensions, the glMapGrid2*() and glEvalMesh2() commands are similar to the one-dimensional versions, except that both u and v information must be included.

void glMapGrid2{fd}(GLint nu, TYPEu1, TYPEu2,
GLint nv, TYPEv1, TYPEv2);
void glEvalMesh2(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2);
Defines a two-dimensional map grid that goes from u1 to u2 in nu evenly spaced steps, from v1 to v2 in nv steps (glMapGrid2*()), and then applies this grid to all enabled evaluators (glEvalMesh2()). The only significant difference from the one-dimensional versions of these two commands is that in glEvalMesh2() the mode parameter can be GL_FILL as well as GL_POINT or GL_LINE. GL_FILL generates filled polygons using the quad-mesh primitive. Stated precisely, glEvalMesh2() is nearly equivalent to one of the following three code fragments. (It's nearly equivalent because when i is equal to nu or j to nv, the parameter is exactly equal to u2 or v2, not to u1+nu*(u2-u1)/nu, which might be slightly different due to round-off error.)
glBegin(GL_POINTS);                /* mode == GL_POINT */

for (i = nu1; i <= nu2; i++)

    for (j = nv1; j <= nv2; j++)

        glEvalCoord2(u1 + i*(u2-u1)/nu, v1+j*(v2-v1)/nv);

glEnd();

or

for (i = nu1; i <= nu2; i++) {     /* mode == GL_LINE */

    glBegin(GL_LINES);

        for (j = nv1; j <= nv2; j++)

            glEvalCoord2(u1 + i*(u2-u1)/nu, v1+j*(v2-v1)/nv);

    glEnd();

}

for (j = nv1; j <= nv2; j++) {

    glBegin(GL_LINES);

    for (i = nu1; i <= nu2; i++)

        glEvalCoord2(u1 + i*(u2-u1)/nu, v1+j*(v2-v1)/nv);

    glEnd();

}

or

for (i = nu1; i < nu2; i++) {     /* mode == GL_FILL */

    glBegin(GL_QUAD_STRIP);

    for (j = nv1; j <= nv2; j++) {

        glEvalCoord2(u1 + i*(u2-u1)/nu, v1+j*(v2-v1)/nv);

        glEvalCoord2(u1 + (i+1)*(u2-u1)/nu, v1+j*(v2-v1)/nv);

    glEnd();

}

Example 12-3 shows the differences necessary to draw the same Bezier surface as Example 12-2, but using glMapGrid2() and glEvalMesh2() to subdivide the square domain into a uniform 8x8 grid. This program also adds lighting and shading, as shown in Figure 12-3.

Figure 12-3 : Lit, Shaded Bezier Surface Drawn with a Mesh

Example 12-3 : Lit, Shaded Bezier Surface Using a Mesh: bezmesh.c

void initlights(void)
{
   GLfloat ambient[] = {0.2, 0.2, 0.2, 1.0};
   GLfloat position[] = {0.0, 0.0, 2.0, 1.0};
   GLfloat mat_diffuse[] = {0.6, 0.6, 0.6, 1.0};
   GLfloat mat_specular[] = {1.0, 1.0, 1.0, 1.0};
   GLfloat mat_shininess[] = {50.0};

   glEnable(GL_LIGHTING);
   glEnable(GL_LIGHT0);

   glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
   glLightfv(GL_LIGHT0, GL_POSITION, position);

   glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
   glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
   glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
}

void display(void)
{
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   glPushMatrix();
   glRotatef(85.0, 1.0, 1.0, 1.0);
   glEvalMesh2(GL_FILL, 0, 20, 0, 20);
   glPopMatrix();
   glFlush();
}

void init(void)
{
   glClearColor(0.0, 0.0, 0.0, 0.0);
   glEnable(GL_DEPTH_TEST);
   glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4,
           0, 1, 12, 4, &ctrlpoints[0][0][0]);
   glEnable(GL_MAP2_VERTEX_3);
   glEnable(GL_AUTO_NORMAL);
   glMapGrid2f(20, 0.0, 1.0, 20, 0.0, 1.0);
   initlights();
}

Using Evaluators for Textures

Example 12-4 enables two evaluators at the same time: The first generates three-dimensional points on the same Bezier surface as Example 12-3, and the second generates texture coordinates. In this case, the texture coordinates are the same as the u and v coordinates of the surface, but a special flat Bezier patch must be created to do this.

The flat patch is defined over a square with corners at (0, 0), (0, 1), (1, 0), and (1, 1); it generates (0, 0) at corner (0, 0), (0, 1) at corner (0, 1), and so on. Since it's of order two (linear degree plus one), evaluating this texture at the point (u, v) generates texture coordinates (s, t). It's enabled at the same time as the vertex evaluator, so both take effect when the surface is drawn. (See "Plate 19" in Appendix I.) If you want the texture to repeat three times in each direction, change every 1.0 in the array texpts[][][] to 3.0. Since the texture wraps in this example, the surface is rendered with nine copies of the texture map.

Example 12-4 : Using Evaluators for Textures: texturesurf.c

#include <GL/gl.h>
#include <GL/glu.h>
#include <stdlib.h>
#include <GL/glut.h>
#include <math.h>

GLfloat ctrlpoints[4][4][3] = {
   {{ -1.5, -1.5, 4.0}, { -0.5, -1.5, 2.0},
    {0.5, -1.5, -1.0}, {1.5, -1.5, 2.0}},
   {{ -1.5, -0.5, 1.0}, { -0.5, -0.5, 3.0},
    {0.5, -0.5, 0.0}, {1.5, -0.5, -1.0}},
   {{ -1.5, 0.5, 4.0}, { -0.5, 0.5, 0.0},
    {0.5, 0.5, 3.0}, {1.5, 0.5, 4.0}},
   {{ -1.5, 1.5, -2.0}, { -0.5, 1.5, -2.0},
    {0.5, 1.5, 0.0}, {1.5, 1.5, -1.0}}
};
GLfloat texpts[2][2][2] = {{{0.0, 0.0}, {0.0, 1.0}},
                        {{1.0, 0.0}, {1.0, 1.0}}};

void display(void)
{
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   glColor3f(1.0, 1.0, 1.0);
   glEvalMesh2(GL_FILL, 0, 20, 0, 20);
   glFlush();
}
#define imageWidth 64
#define imageHeight 64
GLubyte image[3*imageWidth*imageHeight];

void makeImage(void)
{
   int i, j;
   float ti, tj;

   for (i = 0; i < imageWidth; i++) {
      ti = 2.0*3.14159265*i/imageWidth;
      for (j = 0; j < imageHeight; j++) {
         tj = 2.0*3.14159265*j/imageHeight;
         image[3*(imageHeight*i+j)] =
              (GLubyte) 127*(1.0+sin(ti));
         image[3*(imageHeight*i+j)+1] =
              (GLubyte) 127*(1.0+cos(2*tj));
         image[3*(imageHeight*i+j)+2] =
              (GLubyte) 127*(1.0+cos(ti+tj));
      }
   }
}

void init(void)
{
   glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4,
           0, 1, 12, 4, &ctrlpoints[0][0][0]);
   glMap2f(GL_MAP2_TEXTURE_COORD_2, 0, 1, 2, 2,
           0, 1, 4, 2, &texpts[0][0][0]);
   glEnable(GL_MAP2_TEXTURE_COORD_2);
   glEnable(GL_MAP2_VERTEX_3);
   glMapGrid2f(20, 0.0, 1.0, 20, 0.0, 1.0);
   makeImage();
   glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
                   GL_NEAREST);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
                   GL_NEAREST);
   glTexImage2D(GL_TEXTURE_2D, 0, 3, imageWidth, imageHeight, 0,
                GL_RGB, GL_UNSIGNED_BYTE, image);
   glEnable(GL_TEXTURE_2D);
   glEnable(GL_DEPTH_TEST);
   glShadeModel (GL_FLAT);
}

void reshape(int w, int h)
{
   glViewport(0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   if (w <= h)
      glOrtho(-4.0, 4.0, -4.0*(GLfloat)h/(GLfloat)w,
              4.0*(GLfloat)h/(GLfloat)w, -4.0, 4.0);
   else
      glOrtho(-4.0*(GLfloat)w/(GLfloat)h,
              4.0*(GLfloat)w/(GLfloat)h, -4.0, 4.0, -4.0, 4.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
   glRotatef(85.0, 1.0, 1.0, 1.0);
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);
   glutInitWindowSize (500, 500);
   glutInitWindowPosition (100, 100);
   glutCreateWindow (argv[0]);
   init ();
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutMainLoop();
   return 0;
}

The GLU NURBS Interface

Although evaluators are the only OpenGL primitive available to draw curves and surfaces directly, and even though they can be implemented very efficiently in hardware, they're often accessed by applications through higher-level libraries. The GLU provides a NURBS (Non-Uniform Rational B-Spline) interface built on top of the OpenGL evaluator commands.

A Simple NURBS Example

If you understand NURBS, writing OpenGL code to manipulate NURBS curves and surfaces is relatively easy, even with lighting and texture mapping. Follow these steps to draw NURBS curves or untrimmed NURBS surfaces. (See "Trim a NURBS Surface" for information about trimmed surfaces.)

If you intend to use lighting with a NURBS surface, call glEnable() with GL_AUTO_NORMAL to automatically generate surface normals. (Or you can calculate your own.)

Use gluNewNurbsRenderer() to create a pointer to a NURBS object, which is referred to when creating your NURBS curve or surface.

If desired, call gluNurbsProperty() to choose rendering values, such as the maximum size of lines or polygons that are used to render your NURBS object.

Call gluNurbsCallback() if you want to be notified when an error is encountered. (Error checking may slightly degrade performance but is still highly recommended.)

Start your curve or surface by calling gluBeginCurve() or gluBeginSurface().

Generate and render your curve or surface. Call gluNurbsCurve() or gluNurbsSurface() at least once with the control points (rational or nonrational), knot sequence, and order of the polynomial basis function for your NURBS object. You might call these functions additional times to specify surface normals and/or texture coordinates.

Call gluEndCurve() or gluEndSurface() to complete the curve or surface.

Example 12-5 renders a NURBS surface in the shape of a symmetrical hill with control points ranging from -3.0 to 3.0. The basis function is a cubic B-spline, but the knot sequence is nonuniform, with a multiplicity of 4 at each endpoint, causing the basis function to behave like a Bezier curve in each direction. The surface is lighted, with a dark gray diffuse reflection and white specular highlights. Figure 12-4 shows the surface as a lit wireframe.

Figure 12-4 : NURBS Surface

Example 12-5 : NURBS Surface: surface.c

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <stdlib.h>
#include <stdio.h>

GLfloat ctlpoints[4][4][3];
int showPoints = 0;

GLUnurbsObj *theNurb;

void init_surface(void)
{
   int u, v;
   for (u = 0; u < 4; u++) {
      for (v = 0; v < 4; v++) {
         ctlpoints[u][v][0] = 2.0*((GLfloat)u - 1.5);
         ctlpoints[u][v][1] = 2.0*((GLfloat)v - 1.5);

         if ( (u == 1 || u == 2) && (v == 1 || v == 2))
            ctlpoints[u][v][2] = 3.0;
         else
            ctlpoints[u][v][2] = -3.0;
      }
   }
}

void nurbsError(GLenum errorCode)
{
   const GLubyte *estring;

   estring = gluErrorString(errorCode);
   fprintf (stderr, "Nurbs Error: %s\n", estring);
   exit (0);
}

void init(void)
{
   GLfloat mat_diffuse[] = { 0.7, 0.7, 0.7, 1.0 };
   GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
   GLfloat mat_shininess[] = { 100.0 };

   glClearColor (0.0, 0.0, 0.0, 0.0);
   glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
   glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
   glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);

   glEnable(GL_LIGHTING);
   glEnable(GL_LIGHT0);
   glEnable(GL_DEPTH_TEST);
   glEnable(GL_AUTO_NORMAL);
   glEnable(GL_NORMALIZE);

   init_surface();

   theNurb = gluNewNurbsRenderer();
   gluNurbsProperty(theNurb, GLU_SAMPLING_TOLERANCE, 25.0);
   gluNurbsProperty(theNurb, GLU_DISPLAY_MODE, GLU_FILL);
   gluNurbsCallback(theNurb, GLU_ERROR,
                    (GLvoid (*)()) nurbsError);
}

void display(void)
{
   GLfloat knots[8] = {0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0};
   int i, j;

   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

   glPushMatrix();
   glRotatef(330.0, 1.,0.,0.);
   glScalef (0.5, 0.5, 0.5);

   gluBeginSurface(theNurb);
   gluNurbsSurface(theNurb,
                   8, knots, 8, knots,
                   4 * 3, 3, &ctlpoints[0][0][0],
                   4, 4, GL_MAP2_VERTEX_3);
   gluEndSurface(theNurb);

   if (showPoints) {
      glPointSize(5.0);
      glDisable(GL_LIGHTING);
      glColor3f(1.0, 1.0, 0.0);
      glBegin(GL_POINTS);
      for (i = 0; i < 4; i++) {
         for (j = 0; j < 4; j++) {
            glVertex3f(ctlpoints[i][j][0],
                       ctlpoints[i][j][1], ctlpoints[i][j][2]);
         }
      }
      glEnd();
      glEnable(GL_LIGHTING);
   }
   glPopMatrix();
   glFlush();
}

void reshape(int w, int h)
{
   glViewport(0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluPerspective (45.0, (GLdouble)w/(GLdouble)h, 3.0, 8.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
   glTranslatef (0.0, 0.0, -5.0);
}
void keyboard(unsigned char key, int x, int y)
{
   switch (key) {
      case `c':
      case `C':
         showPoints = !showPoints;
         glutPostRedisplay();
         break;
      case 27:
         exit(0);
         break;
      default:
         break;
   }
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);
   glutInitWindowSize (500, 500);
   glutInitWindowPosition (100, 100);
   glutCreateWindow(argv[0]);
   init();
   glutReshapeFunc(reshape);
   glutDisplayFunc(display);
   glutKeyboardFunc (keyboard);
   glutMainLoop();
   return 0;
}

Manage a NURBS Object

As shown in Example 12-5, gluNewNurbsRenderer() returns a new NURBS object, whose type is a pointer to a GLUnurbsObj structure. You must make this object before using any other NURBS routine. When you're done with a NURBS object, you may use gluDeleteNurbsRenderer() to free up the memory that was used.

GLUnurbsObj* gluNewNurbsRenderer (void);
Creates a new NURBS object, nobj. Returns a pointer to the new object, or zero, if OpenGL cannot allocate memory for a new NURBS object.
void gluDeleteNurbsRenderer (GLUnurbsObj *nobj);
Destroys the NURBS object nobj.

Control NURBS Rendering Properties

A set of properties associated with a NURBS object affects the way the object is rendered. These properties include how the surface is rasterized (for example, filled or wireframe) and the precision of tessellation.

void gluNurbsProperty(GLUnurbsObj *nobj, GLenum property,
GLfloat value);
Controls attributes of a NURBS object, nobj. The property argument specifies the property and can be GLU_DISPLAY_MODE, GLU_CULLING, GLU_SAMPLING_METHOD, GLU_SAMPLING_TOLERANCE, GLU_PARAMETRIC_TOLERANCE, GLU_U_STEP, GLU_V_STEP, or GLU_AUTO_LOAD_MATRIX. The value argument indicates what the property should be.
The default value for GLU_DISPLAY_MODE is GLU_FILL, which causes the surface to be rendered as polygons. If GLU_OUTLINE_POLYGON is used for the display-mode property, only the outlines of polygons created by tessellation are rendered. GLU_OUTLINE_PATCH renders the outlines of patches and trimming curves. (See "Create a NURBS Curve or Surface" .)
GLU_CULLING can speed up performance by not performing tessellation if the NURBS object falls completely outside the viewing volume; set this property to GL_TRUE to enable culling (the default is GL_FALSE).
Since a NURBS object is rendered as primitives, it's sampled at different values of its parameter(s) (u and v) and broken down into small line segments or polygons for rendering. If property is GLU_SAMPLING_METHOD, then value is set to one of GLU_PATH_LENGTH (which is the default), GLU_PARAMETRIC_ERROR, or GLU_DOMAIN_DISTANCE, which specifies how a NURBS curve or surface should be tessellated. When value is set to GLU_PATH_LENGTH, the surface is rendered so that the maximum length, in pixels, of the edges of tessellated polygons is no greater than what is specified by GLU_SAMPLING_TOLERANCE. When set to GLU_PARAMETRIC_ERROR, then the value specified by GLU_PARAMETRIC_TOLERANCE is the maximum distance, in pixels, between tessellated polygons and the surfaces they approximate. When set to GLU_DOMAIN_DISTANCE, the application specifies, in parametric coordinates, how many sample points per unit length are taken in the u and v dimensions, using the values for GLU_U_STEP and GLU_V_STEP.
If property is GLU_SAMPLING_TOLERANCE and the sampling method is GLU_PATH_LENGTH, value controls the maximum length, in pixels, to use for tessellated polygons. The default value of 50.0 makes the largest sampled line segment or polygon edge 50.0 pixels long. If property is GLU_PARAMETRIC_TOLERANCE and the sampling method is GLU_PARAMETRIC_ERROR, value controls the maximum distance, in pixels, between the tessellated polygons and the surfaces they approximate. The default value for GLU_PARAMETRIC_TOLERANCE is 0.5, which makes the tessellated polygons within one-half pixel of the approximated surface. If the sampling method is GLU_DOMAIN_DISTANCE and property is either GLU_U_STEP or GLU_V_STEP, then value is the number of sample points per unit length taken along the u or v dimension, respectively, in parametric coordinates. The default for both GLU_U_STEP and GLU_V_STEP is 100.
The GLU_AUTO_LOAD_MATRIX property determines whether the projection matrix, modelview matrix, and viewport are downloaded from the OpenGL server (GL_TRUE, the default), or whether the application must supply these matrices with gluLoadSamplingMatrices() (GL_FALSE).
void gluLoadSamplingMatrices (GLUnurbsObj *nobj, const GLfloat modelMatrix[16], const GLfloat projMatrix[16], const GLint viewport[4]);
If the GLU_AUTO_LOAD_MATRIX is turned off, the modelview and projection matrices and the viewport specified in gluLoadSamplingMatrices() are used to compute sampling and culling matrices for each NURBS curve or surface.

If you need to query the current value for a NURBS property, you may use gluGetNurbsProperty().

void gluGetNurbsProperty (GLUnurbsObj *nobj, GLenum property,
GLfloat *value);
Given the property to be queried for the NURBS object nobj, return its current value.

Handle NURBS Errors

Since there are 37 different errors specific to NURBS functions, it's a good idea to register an error callback to let you know if you've stumbled into one of them. In Example 12-5, the callback function was registered with

gluNurbsCallback(theNurb, GLU_ERROR, (GLvoid (*)()) nurbsError);
void gluNurbsCallback (GLUnurbsObj *nobj, GLenum which,
void (*fn)(GLenum errorCode));
which is the type of callback; it must be GLU_ERROR. When a NURBS function detects an error condition, fn is invoked with the error code as its only argument. errorCode is one of 37 error conditions, named GLU_NURBS_ERROR1 through GLU_NURBS_ERROR37. Use gluErrorString() to describe the meaning of those error codes.

In Example 12-5, the nurbsError() routine was registered as the error callback function:

void nurbsError(GLenum errorCode)
{
   const GLubyte *estring;

   estring = gluErrorString(errorCode);
   fprintf (stderr, "Nurbs Error: %s\n", estring);
   exit (0);
}

Create a NURBS Curve or Surface

To render a NURBS surface, gluNurbsSurface() is bracketed by gluBeginSurface() and gluEndSurface(). The bracketing routines save and restore the evaluator state.

void gluBeginSurface (GLUnurbsObj *nobj);
void gluEndSurface (GLUnurbsObj *nobj);
After gluBeginSurface(), one or more calls to gluNurbsSurface() defines the attributes of the surface. Exactly one of these calls must have a surface type of GL_MAP2_VERTEX_3 or GL_MAP2_VERTEX_4 to generate vertices. Use gluEndSurface() to end the definition of a surface. Trimming of NURBS surfaces is also supported between gluBeginSurface() and gluEndSurface(). (See "Trim a NURBS Surface" .)
void gluNurbsSurface (GLUnurbsObj *nobj, GLint uknot_count,
GLfloat *uknot, GLint vknot_count, GLfloat *vknot,
GLint u_stride, GLint v_stride, GLfloat *ctlarray,
GLint uorder, GLint vorder, GLenum type);
Describes the vertices (or surface normals or texture coordinates) of a NURBS surface, nobj. Several of the values must be specified for both u and v parametric directions, such as the knot sequences (uknot and vknot), knot counts (uknot_count and vknot_count), and order of the polynomial (uorder and vorder) for the NURBS surface. Note that the number of control points isn't specified. Instead, it's derived by determining the number of control points along each parameter as the number of knots minus the order. Then, the number of control points for the surface is equal to the number of control points in each parametric direction, multiplied by one another. The ctlarray argument points to an array of control points.
The last parameter, type, is one of the two-dimensional evaluator types. Commonly, you might use GL_MAP2_VERTEX_3 for nonrational or GL_MAP2_VERTEX_4 for rational control points, respectively. You might also use other types, such as GL_MAP2_TEXTURE_COORD_* or GL_MAP2_NORMAL to calculate and assign texture coordinates or surface normals. For example, to create a lighted (with surface normals) and textured NURBS surface, you may need to call this sequence:
gluBeginSurface(nobj);
   gluNurbsSurface(nobj, ..., GL_MAP2_TEXTURE_COORD_2);
   gluNurbsSurface(nobj, ..., GL_MAP2_NORMAL);
   gluNurbsSurface(nobj, ..., GL_MAP2_VERTEX_3);
gluEndSurface(nobj);
The u_stride and v_stride arguments represent the number of floating-point values between control points in each parametric direction. The evaluator type, as well as its order, affects the u_stride and v_stride values. In Example 12-5, u_stride is 12 (4 * 3) because there are three coordinates for each vertex (set by GL_MAP2_VERTEX_3) and four control points in the parametric v direction; v_stride is 3 because each vertex had three coordinates, and v control points are adjacent to one another.

Drawing a NURBS curve is similar to drawing a surface, except that all calculations are done with one parameter, u, rather than two. Also, for curves, gluBeginCurve() and gluEndCurve() are the bracketing routines.

void gluBeginCurve (GLUnurbsObj *nobj);
void gluEndCurve (GLUnurbsObj *nobj);
After gluBeginCurve(), one or more calls to gluNurbsCurve() define the attributes of the surface. Exactly one of these calls must have a surface type of GL_MAP1_VERTEX_3 or GL_MAP1_VERTEX_4 to generate vertices. Use gluEndCurve() to end the definition of a surface.
void gluNurbsCurve (GLUnurbsObj *nobj, GLint uknot_count,
GLfloat *uknot, GLint u_stride, GLfloat *ctlarray,
GLint uorder, GLenum type);
Defines a NURBS curve for the object nobj. The arguments have the same meaning as those for gluNurbsSurface(). Note that this routine requires only one knot sequence and one declaration of the order of the NURBS object. If this curve is defined within a gluBeginCurve()/gluEndCurve() pair, then the type can be any of the valid one-dimensional evaluator types (such as GL_MAP1_VERTEX_3 or GL_MAP1_VERTEX_4).

Trim a NURBS Surface

To create a trimmed NURBS surface with OpenGL, start as if you were creating an untrimmed surface. After calling gluBeginSurface() and gluNurbsSurface() but before calling gluEndSurface(), start a trim by calling gluBeginTrim().

void gluBeginTrim (GLUnurbsObj *nobj);
void gluEndTrim (GLUnurbsObj *nobj);
Marks the beginning and end of the definition of a trimming loop. A trimming loop is a set of oriented, trimming curve segments (forming a closed curve) that defines the boundaries of a NURBS surface.

You can create two kinds of trimming curves, a piecewise linear curve with gluPwlCurve() or a NURBS curve with gluNurbsCurve(). A piecewise linear curve doesn't look like what's conventionally called a curve, because it's a series of straight lines. A NURBS curve for trimming must lie within the unit square of parametric (u, v) space. The type for a NURBS trimming curve is usually GLU_MAP1_TRIM2. Less often, the type is GLU_MAP1_TRIM3, where the curve is described in a two-dimensional homogeneous space (u', v', w') by (u, v) = (u'/w', v'/w').

void gluPwlCurve (GLUnurbsObj *nobj, GLint count, GLfloat *array,
GLint stride, GLenum type);
Describes a piecewise linear trimming curve for the NURBS object nobj. There are count points on the curve, and they're given by array. The type can be either GLU_MAP1_TRIM_2 (the most common) or GLU_MAP1_TRIM_3 ((u, v, w) homogeneous parameter space). The type affects whether stride, the number of floating-point values to the next vertex, is 2 or 3.

You need to consider the orientation of trimming curves - that is, whether they're counterclockwise or clockwise - to make sure you include the desired part of the surface. If you imagine walking along a curve, everything to the left is included and everything to the right is trimmed away. For example, if your trim consists of a single counterclockwise loop, everything inside the loop is included. If the trim consists of two nonintersecting counterclockwise loops with nonintersecting interiors, everything inside either of them is included. If it consists of a counterclockwise loop with two clockwise loops inside it, the trimming region has two holes in it. The outermost trimming curve must be counterclockwise. Often, you run a trimming curve around the entire unit square to include everything within it, which is what you get by default by not specifying any trimming curves.

Trimming curves must be closed and nonintersecting. You can combine trimming curves, so long as the endpoints of the trimming curves meet to form a closed curve. You can nest curves, creating islands that float in space. Be sure to get the curve orientations right. For example, an error results if you specify a trimming region with two counterclockwise curves, one enclosed within another: The region between the curves is to the left of one and to the right of the other, so it must be both included and excluded, which is impossible. Figure 12-5 illustrates a few valid possibilities.

Figure 12-5 : Parametric Trimming Curves

Figure 12-6 shows the same small hill as in Figure 12-4, this time with a trimming curve that's a combination of a piecewise linear curve and a NURBS curve. The program that creates this figure is similar to that shown in Example 12-5; the differences are in the routines shown in Example 12-6.

Figure 12-6 : Trimmed NURBS Surface

Example 12-6 : Trimming a NURBS Surface: trim.c

void display(void)
{
   GLfloat knots[8] = {0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0};
   GLfloat edgePt[5][2] = /* counter clockwise */
      {{0.0, 0.0}, {1.0, 0.0}, {1.0, 1.0}, {0.0, 1.0},
       {0.0, 0.0}};
   GLfloat curvePt[4][2] = /* clockwise */
      {{0.25, 0.5}, {0.25, 0.75}, {0.75, 0.75}, {0.75, 0.5}};
   GLfloat curveKnots[8] =
      {0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0};
   GLfloat pwlPt[4][2] = /* clockwise */
      {{0.75, 0.5}, {0.5, 0.25}, {0.25, 0.5}};

   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   glPushMatrix();
   glRotatef(330.0, 1.,0.,0.);
   glScalef (0.5, 0.5, 0.5);

   gluBeginSurface(theNurb);
   gluNurbsSurface(theNurb, 8, knots, 8, knots,
                   4 * 3, 3, &ctlpoints[0][0][0],
                   4, 4, GL_MAP2_VERTEX_3);
   gluBeginTrim (theNurb);
      gluPwlCurve (theNurb, 5, &edgePt[0][0], 2,
                   GLU_MAP1_TRIM_2);
   gluEndTrim (theNurb);
   gluBeginTrim (theNurb);
      gluNurbsCurve (theNurb, 8, curveKnots, 2,
                     &curvePt[0][0], 4, GLU_MAP1_TRIM_2);
      gluPwlCurve (theNurb, 3, &pwlPt[0][0], 2,
                   GLU_MAP1_TRIM_2);
   gluEndTrim (theNurb);
   gluEndSurface(theNurb);

   glPopMatrix();
   glFlush();
}

In Example 12-6, gluBeginTrim() and gluEndTrim() bracket each trimming curve. The first trim, with vertices defined by the array edgePt[][], goes counterclockwise around the entire unit square of parametric space. This ensures that everything is drawn, provided it isn't removed by a clockwise trimming curve inside of it. The second trim is a combination of a NURBS trimming curve and a piecewise linear trimming curve. The NURBS curve ends at the points (0.9, 0.5) and (0.1, 0.5), where it is met by the piecewise linear curve, forming a closed clockwise curve.


Chapter 13 - OpenGL Programming Guide

OpenGL Programming Guide
Programming Guide > Chapter 13

Chapter 13
Selection and Feedback

Chapter Objectives

After reading this chapter, you'll be able to do the following:

  • Create applications that allow the user to select a region of the screen or pick an object drawn on the screen
  • Use the OpenGL feedback mode to obtain the results of rendering calculations

Some graphics applications simply draw static images of two- and three-dimensional objects. Other applications allow the user to identify objects on the screen and then to move, modify, delete, or otherwise manipulate those objects. OpenGL is designed to support exactly such interactive applications. Since objects drawn on the screen typically undergo multiple rotations, translations, and perspective transformations, it can be difficult for you to determine which object a user is selecting in a three-dimensional scene. To help you, OpenGL provides a selection mechanism that automatically tells you which objects are drawn inside a specified region of the window. You can use this mechanism together with a special utility routine to determine which object within the region the user is specifying, or picking, with the cursor.

Selection is actually a mode of operation for OpenGL; feedback is another such mode. In feedback mode, you use your graphics hardware and OpenGL to perform the usual rendering calculations. Instead of using the calculated results to draw an image on the screen, however, OpenGL returns (or feeds back) the drawing information to you. For example, if you want to draw three-dimensional objects on a plotter rather than the screen, you would draw the items in feedback mode, collect the drawing instructions, and then convert them to commands the plotter can understand.

In both selection and feedback modes, drawing information is returned to the application rather than being sent to the framebuffer, as it is in rendering mode. Thus, the screen remains frozen - no drawing occurs - while OpenGL is in selection or feedback mode. In these modes, the contents of the color, depth, stencil, and accumulation buffers are not affected. This chapter explains each of these modes in its own section:

  • "Selection" discusses how to use selection mode and related routines to allow a user of your application to pick an object drawn on the screen.
  • "Feedback" describes how to obtain information about what would be drawn on the screen and how that information is formatted.

Selection

Typically, when you're planning to use OpenGL's selection mechanism, you first draw your scene into the framebuffer, and then you enter selection mode and redraw the scene. However, once you're in selection mode, the contents of the framebuffer don't change until you exit selection mode. When you exit selection mode, OpenGL returns a list of the primitives that intersect the viewing volume (remember that the viewing volume is defined by the current modelview and projection matrices and any additional clipping planes, as explained in Chapter 3.) Each primitive that intersects the viewing volume causes a selection hit. The list of primitives is actually returned as an array of integer-valued names and related data - the hit records - that correspond to the current contents of the name stack. You construct the name stack by loading names onto it as you issue primitive drawing commands while in selection mode. Thus, when the list of names is returned, you can use it to determine which primitives might have been selected on the screen by the user.

In addition to this selection mechanism, OpenGL provides a utility routine designed to simplify selection in some cases by restricting drawing to a small region of the viewport. Typically, you use this routine to determine which objects are drawn near the cursor, so that you can identify which object the user is picking. (You can also delimit a selection region by specifying additional clipping planes. Remember that these planes act in world space, not in screen space.) Since picking is a special case of selection, selection is described first in this chapter, and then picking.

The Basic Steps

To use the selection mechanism, you need to perform the following steps.

Specify the array to be used for the returned hit records with glSelectBuffer().

Enter selection mode by specifying GL_SELECT with glRenderMode().

Initialize the name stack using glInitNames() and glPushName().

Define the viewing volume you want to use for selection. Usually this is different from the viewing volume you originally used to draw the scene, so you probably want to save and then restore the current transformation state with glPushMatrix() and glPopMatrix().

Alternately issue primitive drawing commands and commands to manipulate the name stack so that each primitive of interest has an appropriate name assigned.

Exit selection mode and process the returned selection data (the hit records).

The following paragraphs describe glSelectBuffer() and glRenderMode(). In the next section, the commands to manipulate the name stack are described.

void glSelectBuffer(GLsizei size, GLuint *buffer);
Specifies the array to be used for the returned selection data. The buffer argument is a pointer to an array of unsigned integers into which the data is put, and size indicates the maximum number of values that can be stored in the array. You need to call glSelectBuffer() before entering selection mode.
GLint glRenderMode(GLenum mode);
Controls whether the application is in rendering, selection, or feedback mode. The mode argument can be one of GL_RENDER (the default), GL_SELECT, or GL_FEEDBACK. The application remains in a given mode until glRenderMode() is called again with a different argument. Before entering selection mode, glSelectBuffer() must be called to specify the selection array. Similarly, before entering feedback mode, glFeedbackBuffer() must be called to specify the feedback array. The return value for glRenderMode() has meaning if the current render mode (that is, not the mode parameter) is either GL_SELECT or GL_FEEDBACK. The return value is the number of selection hits or the number of values placed in the feedback array when either mode is exited; a negative value means that the selection or feedback array has overflowed. You can use GL_RENDER_MODE with glGetIntegerv() to obtain the current mode.

Creating the Name Stack

As mentioned in the previous section, the name stack forms the basis for the selection information that's returned to you. To create the name stack, first initialize it with glInitNames(), which simply clears the stack, and then add integer names to it while issuing corresponding drawing commands. As you might expect, the commands to manipulate the stack allow you to push a name onto it (glPushName()), pop a name off of it (glPopName()), and replace the name on the top of the stack with a different one (glLoadName()). Example 13-1 shows what your name-stack manipulation code might look like with these commands.

Example 13-1 : Creating a Name Stack

glInitNames();
glPushName(0);

glPushMatrix();   /* save the current transformation state */

    /* create your desired viewing volume here */

    glLoadName(1);
    drawSomeObject();
    glLoadName(2);
    drawAnotherObject();
    glLoadName(3);
    drawYetAnotherObject();
    drawJustOneMoreObject();

glPopMatrix ();   /* restore the previous transformation state*/

In this example, the first two objects to be drawn have their own names, and the third and fourth objects share a single name. With this setup, if either or both of the third and fourth objects causes a selection hit, only one hit record is returned to you. You can have multiple objects share the same name if you don't need to differentiate between them when processing the hit records.

void glInitNames(void);
Clears the name stack so that it's empty.
void glPushName(GLuint name);
Pushes name onto the name stack. Pushing a name beyond the capacity of the stack generates the error GL_STACK_OVERFLOW. The name stack's depth can vary among different OpenGL implementations, but it must be able to contain at least sixty-four names. You can use the parameter GL_NAME_STACK_DEPTH with glGetIntegerv() to obtain the depth of the name stack.
void glPopName(void);
Pops one name off the top of the name stack. Popping an empty stack generates the error GL_STACK_UNDERFLOW.
void glLoadName(GLuint name);
Replaces the value on the top of the name stack with name. If the stack is empty, which it is right after glInitNames() is called, glLoadName() generates the error GL_INVALID_OPERATION. To avoid this, if the stack is initially empty, call glPushName() at least once to put something on the name stack before calling glLoadName().

Calls to glPushName(), glPopName(), and glLoadName() are ignored if you're not in selection mode. You might find that it simplifies your code to use these calls throughout your drawing code, and then use the same drawing code for both selection and normal rendering modes.

The Hit Record

In selection mode, a primitive that intersects the viewing volume causes a selection hit. Whenever a name-stack manipulation command is executed or glRenderMode() is called, OpenGL writes a hit record into the selection array if there's been a hit since the last time the stack was manipulated or glRenderMode() was called. With this process, objects that share the same name - for example, an object that's composed of more than one primitive - don't generate multiple hit records. Also, hit records aren't guaranteed to be written into the array until glRenderMode() is called.

Note: In addition to primitives, valid coordinates produced by glRasterPos() can cause a selection hit. Also, in the case of polygons, no hit occurs if the polygon would have been culled.

Each hit record consists of four items, in order.

  • The number of names on the name stack when the hit occurred.
  • Both the minimum and maximum window-coordinate z values of all vertices of the primitives that intersected the viewing volume since the last recorded hit. These two values, which lie in the range [0,1], are each multiplied by 232-1 and rounded to the nearest unsigned integer.
  • The contents of the name stack at the time of the hit, with the bottommost element first.

When you enter selection mode, OpenGL initializes a pointer to the beginning of the selection array. Each time a hit record is written into the array, the pointer is updated accordingly. If writing a hit record would cause the number of values in the array to exceed the size argument specified with glSelectBuffer(), OpenGL writes as much of the record as fits in the array and sets an overflow flag. When you exit selection mode with glRenderMode(), this command returns the number of hit records that were written (including a partial record if there was one), clears the name stack, resets the overflow flag, and resets the stack pointer. If the overflow flag had been set, the return value is -1.

A Selection Example

In Example 13-2, four triangles (green, red, and two yellow triangles, created by calling drawTriangle()) and a wireframe box representing the viewing volume (drawViewVolume()) are drawn to the screen. Then the triangles are rendered again (selectObjects()), but this time in selection mode. The corresponding hit records are processed in processHits(), and the selection array is printed out. The first triangle generates a hit, the second one doesn't, and the third and fourth ones together generate a single hit.

Example 13-2 : Selection Example: select.c

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <stdlib.h>
#include <stdio.h>

void drawTriangle (GLfloat x1, GLfloat y1, GLfloat x2,
    GLfloat y2, GLfloat x3, GLfloat y3, GLfloat z)
{
   glBegin (GL_TRIANGLES);
   glVertex3f (x1, y1, z);
   glVertex3f (x2, y2, z);
   glVertex3f (x3, y3, z);
   glEnd ();
}

void drawViewVolume (GLfloat x1, GLfloat x2, GLfloat y1,
                     GLfloat y2, GLfloat z1, GLfloat z2)
{
   glColor3f (1.0, 1.0, 1.0);
   glBegin (GL_LINE_LOOP);
   glVertex3f (x1, y1, -z1);
   glVertex3f (x2, y1, -z1);
   glVertex3f (x2, y2, -z1);
   glVertex3f (x1, y2, -z1);
   glEnd ();

   glBegin (GL_LINE_LOOP);
   glVertex3f (x1, y1, -z2);
   glVertex3f (x2, y1, -z2);
   glVertex3f (x2, y2, -z2);
   glVertex3f (x1, y2, -z2);
   glEnd ();

   glBegin (GL_LINES);  /*  4 lines     */
   glVertex3f (x1, y1, -z1);
   glVertex3f (x1, y1, -z2);
   glVertex3f (x1, y2, -z1);
   glVertex3f (x1, y2, -z2);
   glVertex3f (x2, y1, -z1);
   glVertex3f (x2, y1, -z2);
   glVertex3f (x2, y2, -z1);
   glVertex3f (x2, y2, -z2);
   glEnd ();
}

void drawScene (void)
{
   glMatrixMode (GL_PROJECTION);
   glLoadIdentity ();
   gluPerspective (40.0, 4.0/3.0, 1.0, 100.0);

   glMatrixMode (GL_MODELVIEW);
   glLoadIdentity ();
   gluLookAt (7.5, 7.5, 12.5, 2.5, 2.5, -5.0, 0.0, 1.0, 0.0);
   glColor3f (0.0, 1.0, 0.0);   /*  green triangle      */
   drawTriangle (2.0, 2.0, 3.0, 2.0, 2.5, 3.0, -5.0);
   glColor3f (1.0, 0.0, 0.0);   /*  red triangle        */
   drawTriangle (2.0, 7.0, 3.0, 7.0, 2.5, 8.0, -5.0);
   glColor3f (1.0, 1.0, 0.0);   /*  yellow triangles    */
   drawTriangle (2.0, 2.0, 3.0, 2.0, 2.5, 3.0, 0.0);
   drawTriangle (2.0, 2.0, 3.0, 2.0, 2.5, 3.0, -10.0);
   drawViewVolume (0.0, 5.0, 0.0, 5.0, 0.0, 10.0);
}

void processHits (GLint hits, GLuint buffer[])
{
   unsigned int i, j;
   GLuint names, *ptr;

   printf ("hits = %d\n", hits);
   ptr = (GLuint *) buffer;
   for (i = 0; i < hits; i++) { /*  for each hit  */
      names = *ptr;
      printf (" number of names for hit = %d\n", names); ptr++;
      printf("  z1 is %g;", (float) *ptr/0x7fffffff); ptr++;
      printf(" z2 is %g\n", (float) *ptr/0x7fffffff); ptr++;
      printf ("   the name is ");
      for (j = 0; j < names; j++) {     /*  for each name */
         printf ("%d ", *ptr); ptr++;
      }
      printf ("\n");
   }
}

#define BUFSIZE 512

void selectObjects(void)
{
   GLuint selectBuf[BUFSIZE];
   GLint hits;

   glSelectBuffer (BUFSIZE, selectBuf);
   (void) glRenderMode (GL_SELECT);

   glInitNames();
   glPushName(0);

   glPushMatrix ();
   glMatrixMode (GL_PROJECTION);
   glLoadIdentity ();
   glOrtho (0.0, 5.0, 0.0, 5.0, 0.0, 10.0);
   glMatrixMode (GL_MODELVIEW);
   glLoadIdentity ();
   glLoadName(1);
   drawTriangle (2.0, 2.0, 3.0, 2.0, 2.5, 3.0, -5.0);
   glLoadName(2);
   drawTriangle (2.0, 7.0, 3.0, 7.0, 2.5, 8.0, -5.0);
   glLoadName(3);
   drawTriangle (2.0, 2.0, 3.0, 2.0, 2.5, 3.0, 0.0);
   drawTriangle (2.0, 2.0, 3.0, 2.0, 2.5, 3.0, -10.0);
   glPopMatrix ();
   glFlush ();

   hits = glRenderMode (GL_RENDER);
   processHits (hits, selectBuf);
}

void init (void)
{
   glEnable(GL_DEPTH_TEST);
   glShadeModel(GL_FLAT);
}

void display(void)
{
   glClearColor (0.0, 0.0, 0.0, 0.0);
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   drawScene ();
   selectObjects ();
   glFlush();
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);
   glutInitWindowSize (200, 200);
   glutInitWindowPosition (100, 100);
   glutCreateWindow (argv[0]);
   init();
   glutDisplayFunc(display);
   glutMainLoop();
   return 0;
}

Picking

As an extension of the process described in the previous section, you can use selection mode to determine if objects are picked. To do this, you use a special picking matrix in conjunction with the projection matrix to restrict drawing to a small region of the viewport, typically near the cursor. Then you allow some form of input, such as clicking a mouse button, to initiate selection mode. With selection mode established and with the special picking matrix used, objects that are drawn near the cursor cause selection hits. Thus, during picking you're typically determining which objects are drawn near the cursor.

Picking is set up almost exactly like regular selection mode is, with the following major differences.

  • Picking is usually triggered by an input device. In the following code examples, pressing the left mouse button invokes a function that performs picking.
  • You use the utility routine gluPickMatrix() to multiply a special picking matrix onto the current projection matrix. This routine should be called prior to multiplying a standard projection matrix (such as gluPerspective() or glOrtho()). You'll probably want to save the contents of the projection matrix first, so the sequence of operations may look like this:
glMatrixMode (GL_PROJECTION);
glPushMatrix ();
glLoadIdentity ();
gluPickMatrix (...);
gluPerspective, glOrtho, gluOrtho2D, or glFrustum
   /* ... draw scene for picking ; perform picking ... */
glPopMatrix();

Another completely different way to perform picking is described in "Object Selection Using the Back Buffer" in Chapter 14. This technique uses color values to identify different components of an object.

void gluPickMatrix(GLdouble x, GLdouble y, GLdouble width,
GLdouble height, GLint viewport[4]);
Creates a projection matrix that restricts drawing to a small region of the viewport and multiplies that matrix onto the current matrix stack. The center of the picking region is (x, y) in window coordinates, typically the cursor location. width and height define the size of the picking region in screen coordinates. (You can think of the width and height as the sensitivity of the picking device.) viewport[] indicates the current viewport boundaries, which can be obtained by calling
glGetIntegerv(GL_VIEWPORT, GLint *viewport);

Advanced

The net result of the matrix created by gluPickMatrix() is to transform
the clipping region into the unit cube -1 £ (x, y, z) £ 1 (or -w £ (wx, wy, wz) £ w). The picking matrix effectively performs an orthogonal transformation that maps a subregion of this unit cube to the unit cube. Since the transformation is arbitrary, you can make picking work for different sorts
of regions - for example, for rotated rectangular portions of the window. In certain situations, you might find it easier to specify additional clipping planes to define the picking region.

Example 13-3 illustrates simple picking. It also demonstrates how to use multiple names to identify different components of a primitive, in this case the row and column of a selected object. A 3 ´ 3 grid of squares is drawn, with each square a different color. The board[3][3] array maintains the current amount of blue for each square. When the left mouse button is pressed, the pickSquares() routine is called to identify which squares were picked by the mouse. Two names identify each square in the grid - one identifies the row, and the other the column. Also, when the left mouse button is pressed, the color of all squares under the cursor position changes.

Example 13-3 : Picking Example: picksquare.c

#include <GL/gl.h>
#include <GL/glu.h>
#include <stdlib.h>
#include <stdio.h>
#include <GL/glut.h>

int board[3][3];   /*  amount of color for each square  */

/*  Clear color value for every square on the board   */
void init(void)
{
   int i, j;
   for (i = 0; i < 3; i++)
      for (j = 0; j < 3; j ++)
         board[i][j] = 0;
   glClearColor (0.0, 0.0, 0.0, 0.0);
}

void drawSquares(GLenum mode)
{
   GLuint i, j;
   for (i = 0; i < 3; i++) {
      if (mode == GL_SELECT)
         glLoadName (i);
      for (j = 0; j < 3; j ++) {
         if (mode == GL_SELECT)
            glPushName (j);
         glColor3f ((GLfloat) i/3.0, (GLfloat) j/3.0,
                    (GLfloat) board[i][j]/3.0);
         glRecti (i, j, i+1, j+1);
         if (mode == GL_SELECT)
            glPopName ();
      }
   }
}

/*  processHits prints out the contents of the
 *  selection array.
 */
void processHits (GLint hits, GLuint buffer[])
{
   unsigned int i, j;
   GLuint ii, jj, names, *ptr;

   printf ("hits = %d\n", hits);
   ptr = (GLuint *) buffer;
   for (i = 0; i < hits; i++) { /*  for each hit  */
      names = *ptr;
      printf (" number of names for this hit = %d\n", names);
         ptr++;
      printf("  z1 is %g;", (float) *ptr/0x7fffffff); ptr++;
      printf(" z2 is %g\n", (float) *ptr/0x7fffffff); ptr++;
      printf ("   names are ");
      for (j = 0; j < names; j++) { /*  for each name */
         printf ("%d ", *ptr);
         if (j == 0)  /*  set row and column  */
            ii = *ptr;
         else if (j == 1)
            jj = *ptr;
         ptr++;
      }
      printf ("\n");
      board[ii][jj] = (board[ii][jj] + 1) % 3;
   }
}

#define BUFSIZE 512

void pickSquares(int button, int state, int x, int y)
{
   GLuint selectBuf[BUFSIZE];
   GLint hits;
   GLint viewport[4];

   if (button != GLUT_LEFT_BUTTON || state != GLUT_DOWN)
      return;

   glGetIntegerv (GL_VIEWPORT, viewport);

   glSelectBuffer (BUFSIZE, selectBuf);
   (void) glRenderMode (GL_SELECT);

   glInitNames();
   glPushName(0);

   glMatrixMode (GL_PROJECTION);
   glPushMatrix ();
   glLoadIdentity ();
/*  create 5x5 pixel picking region near cursor location      */
   gluPickMatrix ((GLdouble) x, (GLdouble) (viewport[3] - y),
                  5.0, 5.0, viewport);
   gluOrtho2D (0.0, 3.0, 0.0, 3.0);
   drawSquares (GL_SELECT);

   glMatrixMode (GL_PROJECTION);
   glPopMatrix ();
   glFlush ();

   hits = glRenderMode (GL_RENDER);
   processHits (hits, selectBuf);
   glutPostRedisplay();
}

void display(void)
{
   glClear(GL_COLOR_BUFFER_BIT);
   drawSquares (GL_RENDER);
   glFlush();
}

void reshape(int w, int h)
{
   glViewport(0, 0, w, h);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluOrtho2D (0.0, 3.0, 0.0, 3.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
   glutInitWindowSize (100, 100);
   glutInitWindowPosition (100, 100);
   glutCreateWindow (argv[0]);
   init ();
   glutMouseFunc (pickSquares);
   glutReshapeFunc (reshape);
   glutDisplayFunc(display);
   glutMainLoop();
   return 0;
}

Picking with Multiple Names and a Hierarchical Model

Multiple names can also be used to choose parts of a hierarchical object in a scene. For example, if you were rendering an assembly line of automobiles, you might want the user to move the mouse to pick the third bolt on the left front tire of the third car in line. A different name can be used to identify each level of hierarchy: which car, which tire, and finally which bolt. As another example, one name can be used to describe a single molecule among other molecules, and additional names can differentiate individual atoms within that molecule.

Example 13-4 is a modification of Example 3-4, which draws an automobile with four identical wheels, each of which has five identical bolts. Code has been added to manipulate the name stack with the object hierarchy.

Example 13-4 : Creating Multiple Names

draw_wheel_and_bolts()
{
    long i;

    draw_wheel_body();
    for (i = 0; i < 5; i++) {
        glPushMatrix();
            glRotate(72.0*i, 0.0, 0.0, 1.0);
            glTranslatef(3.0, 0.0, 0.0);
            glPushName(i);
                draw_bolt_body();
            glPopName();
        glPopMatrix();
    }
 }

draw_body_and_wheel_and_bolts()
{
    draw_car_body();
    glPushMatrix();
        glTranslate(40, 0, 20);  /* first wheel position*/
        glPushName(1);           /* name of wheel number 1 */
            draw_wheel_and_bolts();
        glPopName();
    glPopMatrix();
    glPushMatrix();
        glTranslate(40, 0, -20); /* second wheel position */
        glPushName(2);           /* name of wheel number 2 */
            draw_wheel_and_bolts();
        glPopName();
    glPopMatrix();

    /* draw last two wheels similarly */
 }

Example 13-5 uses the routines in Example 13-4 to draw three different cars, numbered 1, 2, and 3.

Example 13-5 : Using Multiple Names

draw_three_cars()
{
    glInitNames();
    glPushMatrix();
        translate_to_first_car_position();
        glPushName(1);
            draw_body_and_wheel_and_bolts();
        glPopName();
    glPopMatrix();

    glPushMatrix();
        translate_to_second_car_position();
        glPushName(2);
            draw_body_and_wheel_and_bolts();
        glPopName();
    glPopMatrix();

    glPushMatrix();
        translate_to_third_car_position();
        glPushName(3);
            draw_body_and_wheel_and_bolts();
        glPopName();
    glPopMatrix();
}

Assuming that picking is performed, the following are some possible name-stack return values and their interpretations. In these examples, at most one hit record is returned; also, d1 and d2 are depth values.

2 d1d2 2 1 Car 2, wheel 1

1 d1d2 3 Car 3 body

3 d1d2 1 1 0 Bolt 0 on wheel 1 on car 1

empty The pick was outside all cars

The last interpretation assumes that the bolt and wheel don't occupy the same picking region. A user might well pick both the wheel and the bolt, yielding two hits. If you receive multiple hits, you have to decide which hit to process, perhaps by using the depth values to determine which picked object is closest to the viewpoint. The use of depth values is explored further in the next section.

Picking and Depth Values

Example 13-6 demonstrates how to use depth values when picking to determine which object is picked. This program draws three overlapping rectangles in normal rendering mode. When the left mouse button is pressed, the pickRects() routine is called. This routine returns the cursor position, enters selection mode, initializes the name stack, and multiplies the picking matrix onto the stack before the orthographic projection matrix. A selection hit occurs for each rectangle the cursor is over when the left mouse button is clicked. Finally, the contents of the selection buffer are examined to identify which named objects were within the picking region near the cursor.

The rectangles in this program are drawn at different depth, or z, values. Since only one name is used to identify all three rectangles, only one hit can be recorded. However, if more than one rectangle is picked, that single hit has different minimum and maximum z values.

Example 13-6 : Picking with Depth Values: pickdepth.c

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <stdlib.h>
#include <stdio.h>

void init(void)
{
   glClearColor(0.0, 0.0, 0.0, 0.0);
   glEnable(GL_DEPTH_TEST);
   glShadeModel(GL_FLAT);
   glDepthRange(0.0, 1.0);  /* The default z mapping */
}

void drawRects(GLenum mode)
{
   if (mode == GL_SELECT)
      glLoadName(1);
   glBegin(GL_QUADS);
   glColor3f(1.0, 1.0, 0.0);
   glVertex3i(2, 0, 0);
   glVertex3i(2, 6, 0);
   glVertex3i(6, 6, 0);
   glVertex3i(6, 0, 0);
   glEnd();
   if (mode == GL_SELECT)
      glLoadName(2);
   glBegin(GL_QUADS);
   glColor3f(0.0, 1.0, 1.0);
   glVertex3i(3, 2, -1);
   glVertex3i(3, 8, -1);
   glVertex3i(8, 8, -1);
   glVertex3i(8, 2, -1);
   glEnd();
   if (mode == GL_SELECT)
      glLoadName(3);
   glBegin(GL_QUADS);
   glColor3f(1.0, 0.0, 1.0);
   glVertex3i(0, 2, -2);
   glVertex3i(0, 7, -2);
   glVertex3i(5, 7, -2);
   glVertex3i(5, 2, -2);
   glEnd();
}

void processHits(GLint hits, GLuint buffer[])
{
   unsigned int i, j;
   GLuint names, *ptr;

   printf("hits = %d\n", hits);
   ptr = (GLuint *) buffer;
   for (i = 0; i < hits; i++) {  /* for each hit  */
      names = *ptr;
      printf(" number of names for hit = %d\n", names); ptr++;
      printf("  z1 is %g;", (float) *ptr/0x7fffffff); ptr++;
      printf(" z2 is %g\n", (float) *ptr/0x7fffffff); ptr++;
      printf("   the name is ");
      for (j = 0; j < names; j++) {  /* for each name */
         printf("%d ", *ptr); ptr++;
      }
      printf("\n");
   }
}

#define BUFSIZE 512

void pickRects(int button, int state, int x, int y)
{
   GLuint selectBuf[BUFSIZE];
   GLint hits;
   GLint viewport[4];

   if (button != GLUT_LEFT_BUTTON || state != GLUT_DOWN)
      return;
   glGetIntegerv(GL_VIEWPORT, viewport);

   glSelectBuffer(BUFSIZE, selectBuf);
   (void) glRenderMode(GL_SELECT);

   glInitNames();
   glPushName(0);

   glMatrixMode(GL_PROJECTION);
   glPushMatrix();
   glLoadIdentity();
/*  create 5x5 pixel picking region near cursor location */
   gluPickMatrix((GLdouble) x, (GLdouble) (viewport[3] - y),
                 5.0, 5.0, viewport);
   glOrtho(0.0, 8.0, 0.0, 8.0, -0.5, 2.5);
   drawRects(GL_SELECT);
   glPopMatrix();
   glFlush();

   hits = glRenderMode(GL_RENDER);
   processHits(hits, selectBuf);
}

void display(void)
{
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   drawRects(GL_RENDER);
   glFlush();
}

void reshape(int w, int h)
{
   glViewport(0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   glOrtho(0.0, 8.0, 0.0, 8.0, -0.5, 2.5);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
}

int main(int argc, char **argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);
   glutInitWindowSize (200, 200);
   glutInitWindowPosition (100, 100);
   glutCreateWindow(argv[0]);
   init();
   glutMouseFunc(pickRects);
   glutReshapeFunc(reshape);
   glutDisplayFunc(display);
   glutMainLoop();
   return 0;
}

Try This

  • Modify Example 13-6 to add additional calls to glPushName() so that multiple names are on the stack when the selection hit occurs. What will the contents of the selection buffer be?
  • By default, glDepthRange() sets the mapping of the z values to [0.0,1.0]. Try modifying the glDepthRange() values and see how it affects the z values that are returned in the selection array.

Hints for Writing a Program That Uses Selection

Most programs that allow a user to interactively edit some geometry provide a mechanism for the user to pick items or groups of items for editing. For two-dimensional drawing programs (for example, text editors, page-layout programs, and circuit-design programs), it might be easier to do your own picking calculations instead of using the OpenGL picking mechanism. Often, it's easy to find bounding boxes for two-dimensional objects and to organize them in some hierarchical data structure to speed up searches. For example, picking that uses the OpenGL style in a VLSI layout program containing millions of rectangles can be relatively slow. However, using simple bounding-box information when rectangles are typically aligned with the screen could make picking in such a program extremely fast. The code is probably simpler to write, too.

As another example, since only geometric objects cause hits, you might want to create your own method for picking text. Setting the current raster position is a geometric operation, but it effectively creates only a single pickable point at the current raster position, which is typically at the lower-left corner of the text. If your editor needs to manipulate individual characters within a text string, some other picking mechanism must be used. You could draw little rectangles around each character during picking mode, but it's almost certainly easier to handle text as a special case.

If you decide to use OpenGL picking, organize your program and its data structures so that it's easy to draw appropriate lists of objects in either selection or normal drawing mode. This way, when the user picks something, you can use the same data structures for the pick operation that you use to display the items on the screen. Also, consider whether you want to allow the user to select multiple objects. One way to do this is to store a bit for each item indicating whether it's selected (however, this method requires traversing your entire list of items to find the selected items). You might find it useful to maintain a list of pointers to selected items to speed up this search. It's probably a good idea to keep the selection bit for each item as well, since when you're drawing the entire picture, you might want to draw selected items differently (for example, in a different color or with a selection box around them). Finally, consider the selection user interface. You might want to allow the user to do the following:

  • Select an item
  • Sweep-select a group of items (see the next paragraphs for a description of this behavior)
  • Add an item to the selection
  • Add a sweep selection to the current selections
  • Delete an item from a selection
  • Choose a single item from a group of overlapping items

A typical solution for a two-dimensional drawing program might work as follows.

All selection is done by pointing with the mouse cursor and using the left mouse button. In what follows, cursor means the cursor tied to the mouse, and button means the left mouse button.

Clicking on an item selects it and deselects all other currently selected items. If the cursor is on top of multiple items, the smallest is selected. (In three dimensions, many other strategies work to disambiguate a selection.)

Clicking down where there is no item, holding the button down while dragging the cursor, and then releasing the button selects all the items in a screen-aligned rectangle whose corners are determined by the cursor positions when the button went down and where it came up. This is called a sweep selection. All items not in the swept-out region are deselected. (You must decide whether an item is selected only if it's completely within the sweep region, or if any part of it falls within the region. The completely within strategy usually works best.)

If the Shift key is held down and the user clicks on an item that isn't currently selected, that item is added to the selected list. If the clicked-upon item is selected, it's deleted from the selection list.

If a sweep selection is performed with the Shift key pressed, the items swept out are added to the current selection.

In an extremely cluttered region, it's often hard to do a sweep selection. When the button goes down, the cursor might lie on top of some item, and normally that item would be selected. You can make any operation a sweep selection, but a typical user interface interprets a button-down on an item plus a mouse motion as a select-plus-drag operation. To solve this problem, you can have an enforced sweep selection by holding down, say, the Alt key. With this, the following set of operations constitutes a sweep selection: Alt-button down, sweep, button up. Items under the cursor when the button goes down are ignored.

If the Shift key is held during this sweep selection, the items enclosed in the sweep region are added to the current selection.

Finally, if the user clicks on multiple items, select just one of them. If the cursor isn't moved (or maybe not moved more than a pixel), and the user clicks again in the same place, deselect the item originally selected, and select a different item under the cursor. Use repeated clicks at the same point to cycle through all the possibilities.

Different rules can apply in particular situations. In a text editor, you probably don't have to worry about characters on top of each other, and selections of multiple characters are always contiguous characters in the document. Thus, you need to mark only the first and last selected characters to identify the complete selection. With text, often the best way to handle selection is to identify the positions between characters rather than the characters themselves. This allows you to have an empty selection when the beginning and end of the selection are between the same pair of characters; it also allows you to put the cursor before the first character in the document or after the final one with no special-case code.

In three-dimensional editors, you might provide ways to rotate and zoom between selections, so sophisticated schemes for cycling through the possible selections might be unnecessary. On the other hand, selection in three dimensions is difficult because the cursor's position on the screen usually gives no indication of its depth.

Feedback

Feedback is similar to selection in that once you're in either mode, no pixels are produced and the screen is frozen. Drawing does not occur; instead, information about primitives that would have been rendered is sent back to the application. The key difference between selection and feedback modes is what information is sent back. In selection mode, assigned names are returned to an array of integer values. In feedback mode, information about transformed primitives is sent back to an array of floating-point values. The values sent back to the feedback array consist of tokens that specify what type of primitive (point, line, polygon, image, or bitmap) has been processed and transformed, followed by vertex, color, or other data for that primitive. The values returned are fully transformed by lighting and viewing operations. Feedback mode is initiated by calling glRenderMode() with GL_FEEDBACK as the argument.

Here's how you enter and exit feedback mode.

Call glFeedbackBuffer() to specify the array to hold the feedback information. The arguments to this command describe what type of data and how much of it gets written into the array.

Call glRenderMode() with GL_FEEDBACK as the argument to enter feedback mode. (For this step, you can ignore the value returned by glRenderMode().) After this point, primitives aren't rasterized to produce pixels until you exit feedback mode, and the contents of the framebuffer don't change.

Draw your primitives. While issuing drawing commands, you can make several calls to glPassThrough() to insert markers into the returned feedback data and thus facilitate parsing.

Exit feedback mode by calling glRenderMode() with GL_RENDER as the argument if you want to return to normal drawing mode. The integer value returned by glRenderMode() is the number of values stored in the feedback array.

Parse the data in the feedback array.
void glFeedbackBuffer(GLsizei size, GLenum type, GLfloat *buffer);
Establishes a buffer for the feedback data: buffer is a pointer to an array where the data is stored. The size argument indicates the maximum number of values that can be stored in the array. The type argument describes the information fed back for each vertex in the feedback array; its possible values and their meaning are shown in Table 13-1. glFeedbackBuffer() must be called before feedback mode is entered. In the table, k is 1 in color-index mode and 4 in RGBA mode.
Table 13-1 : glFeedbackBuffer() type Values
type Argument | Coordinates | Color | Texture | Total Values
GL_2D | x, y | - | - | 2
GL_3D | x, y, z | - | - | 3
GL_3D_COLOR | x, y, z | k | - | 3 + k
GL_3D_COLOR_TEXTURE | x, y, z | k | 4 | 7 + k
GL_4D_COLOR_TEXTURE | x, y, z, w | k | 4 | 8 + k

The Feedback Array

In feedback mode, each primitive that would be rasterized (or each call to glBitmap(), glDrawPixels(), or glCopyPixels(), if the raster position is valid) generates a block of values that's copied into the feedback array. The number of values is determined by the type argument to glFeedbackBuffer(), as listed in Table 13-1. Use the appropriate value for the type of primitives you're drawing: GL_2D or GL_3D for unlit two- or three-dimensional primitives, GL_3D_COLOR for lit, three-dimensional primitives, and GL_3D_COLOR_TEXTURE or GL_4D_COLOR_TEXTURE for lit, textured, three- or four-dimensional primitives.

Each block of feedback values begins with a code indicating the primitive type, followed by values that describe the primitive's vertices and associated data. Entries are also written for pixel rectangles. In addition, pass-through markers that you've explicitly created can be returned in the array; the next section explains these markers in more detail. Table 13-2 shows the syntax for the feedback array; remember that the data associated with each returned vertex is as described in Table 13-1. Note that a polygon can have n vertices returned. Also, the x, y, z coordinates returned by feedback are window coordinates; if w is returned, it's in clip coordinates. For bitmaps and pixel rectangles, the coordinates returned are those of the current raster position. In the table, note that GL_LINE_RESET_TOKEN is returned only when the line stipple is reset for that line segment.

Table 13-2 : Feedback Array Syntax

Primitive Type | Code | Associated Data
Point | GL_POINT_TOKEN | vertex
Line | GL_LINE_TOKEN or GL_LINE_RESET_TOKEN | vertex vertex
Polygon | GL_POLYGON_TOKEN | n vertex vertex ... vertex
Bitmap | GL_BITMAP_TOKEN | vertex
Pixel Rectangle | GL_DRAW_PIXEL_TOKEN or GL_COPY_PIXEL_TOKEN | vertex
Pass-through | GL_PASS_THROUGH_TOKEN | a floating-point number

Using Markers in Feedback Mode

Feedback occurs after transformations, lighting, polygon culling, and interpretation of polygons by glPolygonMode(). It might also occur after polygons with more than three edges are broken up into triangles (if your particular OpenGL implementation renders polygons by performing this decomposition). Thus, it might be hard for you to recognize the primitives you drew in the feedback data you receive. To help parse the feedback data, call glPassThrough() as needed in your sequence of drawing commands to insert a marker. You might use the markers to separate the feedback values returned from different primitives, for example. This command causes GL_PASS_THROUGH_TOKEN to be written into the feedback array, followed by the floating-point value you pass in as an argument.

void glPassThrough(GLfloat token);
Inserts a marker into the stream of values written into the feedback array, if called in feedback mode. The marker consists of the code GL_PASS_THROUGH_TOKEN followed by a single floating-point value, token. This command has no effect when called outside of feedback mode. Calling glPassThrough() between glBegin() and glEnd() generates a GL_INVALID_OPERATION error.

A Feedback Example

Example 13-7 demonstrates the use of feedback mode. This program draws a lit, three-dimensional scene in normal rendering mode. Then, feedback mode is entered, and the scene is redrawn. Since the program draws lit, untextured, three-dimensional objects, the type of feedback data is GL_3D_COLOR. Since RGBA mode is used, each unclipped vertex generates seven values for the feedback buffer: x, y, z, r, g, b, and a.

In feedback mode, the program draws two lines as part of a line strip and then inserts a pass-through marker. Next, a point is drawn at (-100.0, -100.0, -100.0), which falls outside the orthographic viewing volume and thus doesn't put any values into the feedback array. Finally, another pass-through marker is inserted, and another point is drawn.

Example 13-7 : Feedback Mode: feedback.c

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <stdlib.h>
#include <stdio.h>

void init(void)
{
   glEnable(GL_LIGHTING);
   glEnable(GL_LIGHT0);
}

void drawGeometry (GLenum mode)
{
   glBegin (GL_LINE_STRIP);
   glNormal3f (0.0, 0.0, 1.0);
   glVertex3f (30.0, 30.0, 0.0);
   glVertex3f (50.0, 60.0, 0.0);
   glVertex3f (70.0, 40.0, 0.0);
   glEnd ();
   if (mode == GL_FEEDBACK)
      glPassThrough (1.0);
   glBegin (GL_POINTS);
   glVertex3f (-100.0, -100.0, -100.0);  /*  will be clipped  */
   glEnd ();
   if (mode == GL_FEEDBACK)
      glPassThrough (2.0);
   glBegin (GL_POINTS);
   glNormal3f (0.0, 0.0, 1.0);
   glVertex3f (50.0, 50.0, 0.0);
   glEnd ();
}

void print3DcolorVertex (GLint size, GLint *count,
                         GLfloat *buffer)
{
   int i;

   printf ("  ");
   for (i = 0; i < 7; i++) {
      printf ("%4.2f ", buffer[size-(*count)]);
      *count = *count - 1;
   }
   printf ("\n");
}

void printBuffer(GLint size, GLfloat *buffer)
{
   GLint count;
   GLfloat token;

   count = size;
   while (count) {
      token = buffer[size-count]; count--;
      if (token == GL_PASS_THROUGH_TOKEN) {
         printf ("GL_PASS_THROUGH_TOKEN\n");
         printf ("  %4.2f\n", buffer[size-count]);
         count--;
      }
      else if (token == GL_POINT_TOKEN) {
         printf ("GL_POINT_TOKEN\n");
         print3DcolorVertex (size, &count, buffer);
      }
      else if (token == GL_LINE_TOKEN) {
         printf ("GL_LINE_TOKEN\n");
         print3DcolorVertex (size, &count, buffer);
         print3DcolorVertex (size, &count, buffer);
      }
      else if (token == GL_LINE_RESET_TOKEN) {
         printf ("GL_LINE_RESET_TOKEN\n");
         print3DcolorVertex (size, &count, buffer);
         print3DcolorVertex (size, &count, buffer);
      }
   }
}

void display(void)
{
   GLfloat feedBuffer[1024];
   GLint size;

   glMatrixMode (GL_PROJECTION);
   glLoadIdentity ();
   glOrtho (0.0, 100.0, 0.0, 100.0, 0.0, 1.0);

   glClearColor (0.0, 0.0, 0.0, 0.0);
   glClear(GL_COLOR_BUFFER_BIT);
   drawGeometry (GL_RENDER);
   glFeedbackBuffer (1024, GL_3D_COLOR, feedBuffer);
   (void) glRenderMode (GL_FEEDBACK);
   drawGeometry (GL_FEEDBACK);

   size = glRenderMode (GL_RENDER);
   printBuffer (size, feedBuffer);
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
   glutInitWindowSize (100, 100);
   glutInitWindowPosition (100, 100);
   glutCreateWindow(argv[0]);
   init();
   glutDisplayFunc(display);
   glutMainLoop();
   return 0;
}

Running this program generates the following output:

GL_LINE_RESET_TOKEN
 30.00 30.00 0.00 0.84 0.84 0.84 1.00
 50.00 60.00 0.00 0.84 0.84 0.84 1.00
GL_LINE_TOKEN
 50.00 60.00 0.00 0.84 0.84 0.84 1.00
 70.00 40.00 0.00 0.84 0.84 0.84 1.00
GL_PASS_THROUGH_TOKEN
 1.00
GL_PASS_THROUGH_TOKEN
 2.00
GL_POINT_TOKEN
 50.00 50.00 0.00 0.84 0.84 0.84 1.00

Thus, the line strip drawn with these commands results in two primitives:

glBegin(GL_LINE_STRIP);
    glNormal3f (0.0, 0.0, 1.0);
    glVertex3f (30.0, 30.0, 0.0);
    glVertex3f (50.0, 60.0, 0.0);
    glVertex3f (70.0, 40.0, 0.0);
glEnd();

The first primitive begins with GL_LINE_RESET_TOKEN, which indicates that the primitive is a line segment and that the line stipple is reset. The second primitive begins with GL_LINE_TOKEN, so it's also a line segment, but the line stipple isn't reset and hence continues from where the previous line segment left off. Each of the two vertices for these lines generates seven values for the feedback array. Note that the RGBA values for all four vertices in these two lines are (0.84, 0.84, 0.84, 1.0), which is a very light gray color with the maximum alpha value. These color values are a result of the interaction of the surface normal and lighting parameters.

Since no feedback data is generated between the first and second pass-through markers, you can deduce that any primitives drawn between the first two calls to glPassThrough() were clipped out of the viewing volume. Finally, the point at (50.0, 50.0, 0.0) is drawn, and its associated data is copied into the feedback array.

Note: In both feedback and selection modes, information on objects is returned prior to any fragment tests. Thus, objects that would not be drawn due to failure of the scissor, alpha, depth, or stencil tests may still have their data processed and returned in both feedback and selection modes.

Try This

Make changes to Example 13-7 and see how they affect the feedback values that are returned. For example, change the coordinate values of glOrtho(). Change the lighting variables, or eliminate lighting altogether and change the feedback type to GL_3D. Or add more primitives to see what other geometry (such as filled polygons) contributes to the feedback array.


Chapter 14 - OpenGL Programming Guide

OpenGL Programming Guide
Programming Guide > Chapter 14

Chapter 14
Now That You Know

Chapter Objectives

This chapter doesn't have objectives in the same way that previous chapters do. It's simply a collection of topics that describe ideas you might find useful for your application. Some topics, such as error handling, don't fit into other categories, but are too short for an entire chapter.

OpenGL is kind of a bag of low-level tools; now that you know about those tools, you can use them to implement higher-level functions. This chapter presents several examples of such higher-level capabilities.

This chapter discusses a variety of techniques based on OpenGL commands that illustrate some of the not-so-obvious uses to which you can put these commands. The examples are in no particular order and aren't related to each other. The idea is to read the section headings and skip to the examples that you find interesting. For your convenience, the headings are listed and explained briefly here.

Note: Most of the examples in the rest of this guide are complete and can be compiled and run as is. In this chapter, however, there are no complete programs, and you have to do a bit of work on your own to make them run.

  • "Error Handling" tells you how to check for OpenGL error conditions.
  • "Which Version Am I Using?" describes how to find out details about the implementation, including the version number. This can be useful for writing applications that are backward compatible with earlier versions of OpenGL.
  • "Extensions to the Standard" presents techniques to identify and use vendor-specific extensions to the OpenGL standard.
  • "Cheesy Translucency" explains how to use polygon stippling to achieve translucency; this is particularly useful when you don't have blending hardware available.
  • "An Easy Fade Effect" shows how to use polygon stippling to create the effect of a fade into the background.
  • "Object Selection Using the Back Buffer" describes how to use the back buffer in a double-buffered system to handle simple object picking.
  • "Cheap Image Transformation" discusses how to draw a distorted version of a bitmapped image by drawing each pixel as a quadrilateral.
  • "Displaying Layers" explains how to display multiple different layers of materials and indicate where the materials overlap.
  • "Antialiased Characters" describes how to draw smoother fonts.
  • "Drawing Round Points" describes how to draw near-round points.
  • "Interpolating Images" shows how to smoothly blend from one image to the another.
  • "Making Decals" explains how to draw two images, where one is a sort of decal that should always appear on top of the other.
  • "Drawing Filled, Concave Polygons Using the Stencil Buffer" tells you how to draw concave polygons, nonsimple polygons, and polygons with holes by using the stencil buffer.
  • "Finding Interference Regions" describes how to determine where three-dimensional pieces overlap.
  • "Shadows" describes how to draw shadows of lit objects.
  • "Hidden-Line Removal" discusses how to draw a wireframe object with hidden lines removed by using the stencil buffer.
  • "Texture-Mapping Applications" describes several clever uses for texture mapping, such as rotating and warping images.
  • "Drawing Depth-Buffered Images" tells you how to combine images in a depth-buffered environment.
  • "Dirichlet Domains" explains how to find the Dirichlet domain of a set of points using the depth buffer.
  • "Life in the Stencil Buffer" explains how to implement the Game of Life using the stencil buffer.
  • "Alternative Uses for glDrawPixels() and glCopyPixels()" describes how to use these two commands for such effects as fake video, airbrushing, and transposed images.

Error Handling

The truth is, your program will make mistakes. Use of error-handling routines are essential during development and are highly recommended for commercially released applications. (Unless you can give a 100% guarantee your program will never generate an OpenGL error condition. Get real!) OpenGL has simple error-handling routines for the base GL and GLU libraries.

When OpenGL detects an error (in either the base GL or GLU), it records a current error code. The command that caused the error is ignored, so it has no effect on OpenGL state or on the framebuffer contents. (If the error recorded was GL_OUT_OF_MEMORY, however, the results of the command are undefined.) Once recorded, the current error code isn't cleared - that is, additional errors aren't recorded - until you call the query command glGetError(), which returns the current error code. After you've queried and cleared the current error code, or if there's no error to begin with, glGetError() returns GL_NO_ERROR.

GLenum glGetError(void);
Returns the value of the error flag. When an error occurs in either the GL or GLU, the error flag is set to the appropriate error code value. If GL_NO_ERROR is returned, there has been no detectable error since the last call to glGetError(), or since the GL was initialized. No other errors are recorded until glGetError() is called, the error code is returned, and the flag is reset to GL_NO_ERROR.

It is strongly recommended that you call glGetError() at least once in each display() routine. Table 14-1 lists the basic defined OpenGL error codes.

Table 14-1 : OpenGL Error Codes

Error Code | Description
GL_INVALID_ENUM | GLenum argument out of range
GL_INVALID_VALUE | Numeric argument out of range
GL_INVALID_OPERATION | Operation illegal in current state
GL_STACK_OVERFLOW | Command would cause a stack overflow
GL_STACK_UNDERFLOW | Command would cause a stack underflow
GL_OUT_OF_MEMORY | Not enough memory left to execute command

There are also thirty-seven GLU NURBS errors (with non-descriptive constant names, GLU_NURBS_ERROR1, GLU_NURBS_ERROR2, and so on), fourteen tessellator errors (GLU_TESS_MISSING_BEGIN_POLYGON, GLU_TESS_MISSING_END_POLYGON, GLU_TESS_MISSING_BEGIN_CONTOUR, GLU_TESS_MISSING_END_CONTOUR, GLU_TESS_COORD_TOO_LARGE, GLU_TESS_NEED_COMBINE_CALLBACK, and eight generically named GLU_TESS_ERROR*), and GLU_INCOMPATIBLE_GL_VERSION. Also, the GLU defines the error codes GLU_INVALID_ENUM, GLU_INVALID_VALUE, and GLU_OUT_OF_MEMORY, which have the same meaning as the related OpenGL codes.

To obtain a printable, descriptive string corresponding to either a GL or GLU error code, use the GLU routine gluErrorString().

const GLubyte* gluErrorString(GLenum errorCode);
Returns a pointer to a descriptive string that corresponds to the OpenGL or GLU error number passed in errorCode.

In Example 14-1, a simple error handling routine is shown.

Example 14-1 : Querying and Printing an Error

GLenum errCode;
const GLubyte *errString;

if ((errCode = glGetError()) != GL_NO_ERROR) {
    errString = gluErrorString(errCode);
   fprintf (stderr, "OpenGL Error: %s\n", errString);
}

Note: The string returned by gluErrorString() must not be altered or freed by the application.

Which Version Am I Using?

The portability of OpenGL applications is one of OpenGL's attractive features. However, new versions of OpenGL introduce new features, which may introduce backward compatibility problems. In addition, you may want your application to perform equally well on a variety of implementations. For example, you might make texture mapping the default rendering mode on one machine, but only have flat shading on another. You can use glGetString() to obtain release information about your OpenGL implementation.

const GLubyte* glGetString(GLenum name);
Returns a pointer to a string that describes an aspect of the OpenGL implementation. name can be one of the following: GL_VENDOR, GL_RENDERER, GL_VERSION, or GL_EXTENSIONS.

GL_VENDOR returns the name of the company responsible for the OpenGL implementation. GL_RENDERER returns an identifier of the renderer, which is usually the hardware platform. For more about GL_EXTENSIONS, see the next section, "Extensions to the Standard."

GL_VERSION returns a string that identifies the version number of this implementation of OpenGL. The version string is laid out as follows:

<version number><space><vendor-specific information>

The version number is either of the form

major_number.minor_number

or

major_number.minor_number.release_number

where the numbers all have one or more digits. The vendor-specific information is optional. For example, if this OpenGL implementation is from the fictitious XYZ Corporation, the string returned might be

1.1.4 XYZ-OS 3.2

which means that this implementation is XYZ's fourth release of an OpenGL library that conforms to the specification for OpenGL Version 1.1. It probably also means this is release 3.2 of XYZ's proprietary operating system.

Another way to query the version number for OpenGL is to look for the symbolic constant (use the preprocessor statement #ifdef) named GL_VERSION_1_1. The absence of the constant GL_VERSION_1_1 means that you have OpenGL Version 1.0.

Note: If running from client to server, such as when performing indirect rendering with the OpenGL extension to the X Window System, the client and server may be different versions. If your client version is ahead of your server, your client might request an operation that is not supported on your server.

Utility Library Version

gluGetString() is a query function for the Utility Library (GLU) and is similar to glGetString().

const GLubyte* gluGetString(GLenum name);
Returns a pointer to a string that describes an aspect of the OpenGL implementation. name can be one of the following: GLU_VERSION, or GLU_EXTENSIONS.

Note that gluGetString() was not available in GLU 1.0. Another way to query the version number for GLU is to look for the symbolic constant GLU_VERSION_1_1. The absence of the constant GLU_VERSION_1_1 means that you have GLU 1.0.

Extensions to the Standard

OpenGL has a formal written specification that describes what operations comprise the library. An individual vendor or a group of vendors may decide to include additional functionality to their released implementation.

New routine and symbolic constant names clearly indicate whether a feature is part of the OpenGL standard or a vendor-specific extension. To make a vendor-specific name, the vendor appends a company identifier (in uppercase) and, if needed, additional information, such as a machine name. For example, if XYZ Corporation wants to add a new routine and symbolic constant, they might be of the form glCommandXYZ() and GL_DEFINITION_XYZ. If XYZ Corporation wants to have an extension that is available only on its FooBar graphics board, then the names might be glCommandXYZfb() and GL_DEFINITION_XYZ_FB.

If two of more vendors agree to implement the same extension, then the procedures and constants are suffixed with the more generic EXT (glCommandEXT() and GL_DEFINITION_EXT).

If you want to know if a particular extension is supported on your implementation, use glGetString(GL_EXTENSIONS). This returns a list of all the extensions in the implementation, separated by spaces. If you want to find out if a specific extension is supported, use the code in Example 14-2 to search through the list and match the extension name. Return GL_TRUE, if it is; GL_FALSE, if it isn't.

Example 14-2 : Find Out If An Extension Is Supported

static GLboolean QueryExtension(char *extName)
{
  char *p = (char *) glGetString(GL_EXTENSIONS);
  char *end = p + strlen(p);
  while (p < end) {
   int n = strcspn(p, " ");
     if ((strlen(extName)==n) && (strncmp(extName,p,n)==0)) {
    return GL_TRUE;
   }
   p += (n + 1);
  }
  return GL_FALSE;
  }

Cheesy Translucency

You can use polygon stippling to simulate a translucent material. This is an especially good solution for systems that don't have blending hardware. Since polygon stipple patterns are 32x32 bits, or 1024 bits, you can go from opaque to transparent in 1023 steps. (In practice, that's many more steps than you need!) For example, if you want a surface that lets through 29 percent of the light, simply make up a stipple pattern where 29 percent (roughly 297) of the pixels in the mask are zero and the rest are one. Even if your surfaces have the same translucency, don't use the same stipple pattern for each one, as they cover exactly the same bits on the screen. Make up a different pattern for each by randomly selecting the appropriate number of pixels to be zero. (See "Displaying Points, Lines, and Polygons" in Chapter 2 for more information about polygon stippling.)

If you don't like the effect with random pixels turned on, you can use regular patterns, but they don't work as well when transparent surfaces are stacked. This is often not a problem because most scenes have relatively few translucent regions that overlap. In a picture of an automobile with translucent windows, your line of sight can go through at most two windows, and usually it's only one.

An Easy Fade Effect

Suppose you have an image that you want to fade gradually to some background color. Define a series of polygon stipple patterns, each of which has more bits turned on so that they represent denser and denser patterns. Then use these patterns repeatedly with a polygon large enough to cover the region over which you want to fade. For example, suppose you want to fade to black in 16 steps. First define 16 different pattern arrays:

GLubyte stips[16][4*32];

Then load them in such a way that each has one-sixteenth of the pixels in a 32 ´ 32 stipple pattern turned on and that the bitwise OR of all the stipple patterns is all ones. After that, the following code does the trick:

draw_the_picture();
glColor3f(0.0, 0.0, 0.0);    /* set color to black */
for (i = 0; i < 16; i++) {
    glPolygonStipple(&stips[i][0]);
    draw_a_polygon_large_enough_to_cover_the_whole_region();
}

In some OpenGL implementations, you might get better performance by first compiling the stipple patterns into display lists. During your initialization, do something like this:

#define STIP_OFFSET 100
for (i = 0; i < 16; i++) {
    glNewList(i+STIP_OFFSET, GL_COMPILE);
    glPolygonStipple(&stips[i][0]);
    glEndList();
}

Then, replace this line in the first code fragment

glPolygonStipple(&stips[i][0]);

with

glCallList(i);

By compiling the command to set the stipple into a display list, OpenGL might be able to rearrange the data in the stips[ ][ ] array into the hardware-specific form required for maximum stipple-setting speed.

Another application for this technique is if you're drawing a changing picture and want to leave some blur behind that gradually fades out to give some indication of past motion. For example, suppose you're simulating a planetary system and you want to leave trails on the planets to show a recent portion of their path. Again, assuming you want to fade in sixteen steps, set up the stipple patterns as before (using the display-list version, say), and have the main simulation loop look something like this:

current_stipple = 0;
while (1) {                         /* loop forever */
    draw_the_next_frame();
    glCallList(current_stipple++);
    if (current_stipple == 16) current_stipple = 0;
    glColor3f(0.0, 0.0, 0.0);       /* set color to black */
    draw_a_polygon_large_enough_to_cover_the_whole_region();
}

Each time through the loop, you clear one-sixteenth of the pixels. Any pixel that hasn't had a planet on it for sixteen frames is certain to be cleared to black. Of course, if your system supports blending in hardware, it's easier to blend in a certain amount of background color with each frame. (See "Displaying Points, Lines, and Polygons" in Chapter 2 for polygon stippling details, Chapter 7 for more information about display lists, and "Blending" in Chapter 6 for information about blending.)

Object Selection Using the Back Buffer

Although the OpenGL selection mechanism (see "Selection" in Chapter 13) is powerful and flexible, it can be cumbersome to use. Often, the situation is simple: Your application draws a scene composed of a substantial number of objects; the user points to an object with the mouse, and the application needs to find the item under the tip of the cursor.

One way to do this requires your application to be running in double-buffer mode. When the user picks an object, the application redraws the entire scene in the back buffer, but instead of using the normal colors for objects, it encodes some kind of object identifier for each object's color. The application then simply reads back the pixel under the cursor, and the value of that pixel encodes the number of the picked object. If many picks are expected for a single, static picture, you can read the entire color buffer once and look in your copy for each attempted pick, rather than read back each pixel individually.

Note that this scheme has an advantage over standard selection in that it picks the object that's in front if multiple objects appear at the same pixel, one behind the other. Since the image with false colors is drawn in the back buffer, the user never sees it; you can redraw the back buffer (or copy it from the front buffer) before swapping the buffers. In color-index mode, the encoding is simple - send the object identifier as the index. In RGBA mode, encode the bits of the identifier into the R, G, and B components.

Be aware that you can run out of identifiers if there are too many objects in the scene. For example, suppose you're running in color-index mode on a system that has 4-bit buffers for color-index information (16 possible different indices) in each of the color buffers, but the scene has thousands of pickable items. To address this issue, the picking can be done in a few passes. To think about this in concrete terms, assume there are fewer than 4096 items, so all the object identifiers can be encoded in 12 bits. In the first pass, draw the scene using indices composed of the 4 high-order bits, then use the second and third passes to draw the middle 4 bits and the 4 low-order bits. After each pass, read the pixel under the cursor, extract the bits, and pack them together at the end to get the object identifier.

With this method, the picking takes three times as long, but that's often acceptable. Note that after you have the high-order 4 bits, you eliminate 15/16 of all objects, so you really need to draw only 1/16 of them for the second pass. Similarly, after the second pass, 255 of the 256 possible items have been eliminated. The first pass thus takes about as long as drawing a single frame does, but the second and third passes can be up to 16 and 256 times as fast.

If you're trying to write portable code that works on different systems, break up your object identifiers into chunks that fit on the lowest common denominator of those systems. Also, keep in mind that your system might perform automatic dithering in RGB mode. If this is the case, turn off dithering.

Cheap Image Transformation

If you want to draw a distorted version of a bitmapped image (perhaps simply stretched or rotated, or perhaps drastically modified by some mathematical function), there are many possibilities. You can use the image as a texture map, which allows you to scale, rotate, or otherwise distort the image. If you just want to scale the image, you can use glPixelZoom().

In many cases, you can achieve good results by drawing the image of each pixel as a quadrilateral. Although this scheme doesn't produce images that are as nice as those you would get by applying a sophisticated filtering algorithm (and it might not be sufficient for sophisticated users), it's a lot quicker.

To make the problem more concrete, assume that the original image is m pixels by n pixels, with coordinates chosen from [0, m-1] ´ [0, n-1]. Let the distortion functions be x(m,n) and y(m,n). For example, if the distortion is simply a zooming by a factor of 3.2, then x(m,n) = 3.2*m and y(m,n) = 3.2*n. The following code draws the distorted image:

glShadeModel(GL_FLAT);
glScale(3.2, 3.2, 1.0);
for (j=0; j < n; j++) {
    glBegin(GL_QUAD_STRIP);
    for (i=0; i <= m; i++) {
        glVertex2i(i,j);
        glVertex2i(i, j+1);
        set_color(i,j);
    }
    glEnd();
}

This code draws each transformed pixel in a solid color equal to that pixel's color and scales the image size by 3.2. The routine set_color() stands for whatever the appropriate OpenGL command is to set the color of the image pixel.

The following is a slightly more complex version that distorts the image using the functions x(i,j) and y(i,j):

glShadeModel(GL_FLAT);
for (j=0; j < n; j++) {
    glBegin(GL_QUAD_STRIP);
    for (i=0; i <= m; i++) {
        glVertex2i(x(i,j), y(i,j));
        glVertex2i(x(i,j+1), y(i,j+1));
        set_color(i,j);
    }
    glEnd();
}

An even better distorted image can be drawn with the following code:

glShadeModel(GL_SMOOTH);
for (j=0; j < (n-1); j++) {
    glBegin(GL_QUAD_STRIP);
    for (i=0; i < m; i++) {
        set_color(i,j);
        glVertex2i(x(i,j), y(i,j));
        set_color(i,j+1);
        glVertex2i(x(i,j+1), y(i,j+1));
    }
    glEnd();
}

This code smoothly interpolates color across each quadrilateral. Note that this version produces one fewer quadrilateral in each dimension than do the flat-shaded versions, because the color image is being used to specify colors at the quadrilateral vertices. In addition, you can antialias the polygons with the appropriate blending function (GL_SRC_ALPHA, GL_ONE) to get an even nicer image.

Displaying Layers

In some applications such as semiconductor layout programs, you want to display multiple different layers of materials and indicate where the materials overlap each other.

As a simple example, suppose you have three different substances that can be layered. At any point, eight possible combinations of layers can occur, as shown in Table 14-2.

Table 14-2 : Eight Combinations of Layers

 | Layer 1 | Layer 2 | Layer 3 | Color
0 | absent | absent | absent | black
1 | present | absent | absent | red
2 | absent | present | absent | green
3 | present | present | absent | blue
4 | absent | absent | present | pink
5 | present | absent | present | yellow
6 | absent | present | present | white
7 | present | present | present | gray

You want your program to display eight different colors, depending on the layers present. One arbitrary possibility is shown in the last column of the table. To use this method, use color-index mode and load your color map so that entry 0 is black, entry 1 is red, entry 2 is green, and so on. Note that if the numbers from 0 through 7 are written in binary, the 4 bit is turned on whenever layer 3 appears, the 2 bit whenever layer 2 appears, and the 1 bit whenever layer 1 appears.

To clear the window, set the writemask to 7 (all three layers) and set the clearing color to 0. To draw your image, set the color to 7, and then when you want to draw something in layer n, set the writemask to n. In other types of applications, it might be necessary to selectively erase in a layer, in which case you would use the writemasks just discussed, but set the color to 0 instead of 7. (See "Masking Buffers" in Chapter 10 for more information about writemasks.)

Antialiased Characters

Using the standard technique for drawing characters with glBitmap(), drawing each pixel of a character is an all-or-nothing affair - the pixel is either turned on or not. If you're drawing black characters on a white background, for example, the resulting pixels are either black or white, never a shade of gray. Much smoother, higher-quality images can be achieved if intermediate colors are used when rendering characters (grays, in this example).

Assuming that you're drawing black characters on a white background, imagine a highly magnified picture of the pixels on the screen, with a high-resolution character outline superimposed on it, as shown in the left side of Figure 14-1.

Figure 14-1 : Antialiased Characters

Notice that some of the pixels are completely enclosed by the character's outline and should be painted black; some pixels are completely outside the outline and should be painted white; but many pixels should ideally be painted some shade of gray, where the darkness of the gray corresponds to the amount of black in the pixel. If this technique is used, the resulting image on the screen looks better.

If speed and memory usage are of no concern, each character can be drawn as a small image instead of as a bitmap. If you're using RGBA mode, however, this method might require up to 32 bits per pixel of the character to be stored and drawn, instead of the 1 bit per pixel in a standard character. Alternatively, you could use one 8-bit index per pixel and convert these indices to RGBA by table lookup during transfer. In many cases, a compromise is possible that allows you to draw the character with a few gray levels between black and white (say, two or three), and the resulting font description requires only 2 or 3 bits per pixel of storage.

The numbers in the right side of Figure 14-1 indicate the approximate percentage coverage of each pixel: 0 means approximately empty, 1 means approximately one-third coverage, 2 means two-thirds, and 3 means completely covered. If pixels labeled 0 are painted white, pixels labeled 3 are painted black, and pixels labeled 1 and 2 are painted one-third and two-thirds black, respectively, the resulting character looks quite good. Only 2 bits are required to store the numbers 0, 1, 2, and 3, so for 2 bits per pixel, four levels of gray can be saved.

There are basically two methods to implement antialiased characters, depending on whether you're in RGBA or color-index mode.

In RGBA mode, define three different character bitmaps, corresponding to where 1, 2, and 3 appear in Figure 14-1. Set the color to white, and clear for the background. Set the color to one-third gray (RGB = (0.666, 0.666, 0.666)), and draw all the pixels with a 1 in them. Then set RGB = (0.333, 0.333, 0.333), draw with the 2 bitmap, and use RGB = (0.0, 0.0, 0.0) for the 3 bitmap. What you're doing is defining three different fonts and redrawing the string three times, where each pass fills in the bits of the appropriate color densities.

In color-index mode, you can do exactly the same thing, but if you're willing to set up the color map correctly and use writemasks, you can get away with only two bitmaps per character and two passes per string. In the preceding example, set up one bitmap that has a 1 wherever 1 or 3 appears in the character. Set up a second bitmap that has a 1 wherever a 2 or a 3 appears. Load the color map so that 0 gives white, 1 gives light gray, 2 gives dark gray, and 3 gives black. Set the color to 3 (11 in binary) and the writemask to 1, and draw the first bitmap. Then change the writemask to 2, and draw the second. Where 0 appears in Figure 14-1, nothing is drawn in the framebuffer. Where 1, 2, and 3 appear, 1, 2, and 3 appear in the framebuffer.

For this example with only four gray levels, the savings is small - two passes instead of three. If eight gray levels were used instead, the RGBA method would require seven passes, and the color-map masking technique would require only three. With sixteen gray levels, the comparison is fifteen passes to four passes. (See "Masking Buffers" in Chapter 10 for more information about writemasks and "Bitmaps and Fonts" in Chapter 8 for more information about drawing bitmaps.)

Try This

  • Can you see how to do RGBA rendering using no more images than the optimized color-index case? Hint: How are RGB fragments normally merged into the color buffer when antialiasing is desired?

Drawing Round Points

Draw near-round, aliased points by enabling point antialiasing, turning blending off, and using an alpha function that passes only fragments with alpha greater than 0.5. (See "Antialiasing" and "Blending" in Chapter 6 for more information about these topics.)

Interpolating Images

Suppose you have a pair of images (where image can mean a bitmap image, or a picture generated using geometry in the usual way), and you want to smoothly blend from one to the other. This can be done easily using the alpha component and appropriate blending operations. Let's say you want to accomplish the blending in ten steps, where image A is shown in frame 0 and image B is shown in frame 9. The obvious approach is to draw image A with alpha equal to (9- &igr; )/9 and image B with an alpha of i/9 in frame i.

The problem with this method is that both images must be drawn in each frame. A faster approach is to draw image A in frame 0. To get frame 1, blend in 1/9 of image B and 8/9 of what's there. For frame 2, blend in 1/8 of image B with 7/8 of what's there. For frame 3, blend in 1/7 of image B with 6/7 of what's there, and so on. For the last step, you're just drawing 1/1 of image B blended with 0/1 of what's left, yielding image B exactly.

To see that this works, if for frame i you have

and you blend in B/(9- &igr; ) with (8- &igr; )/(9- &igr; ) of what's there, you get

(See "Blending" in Chapter 6.)

Making Decals

Suppose you're drawing a complex three-dimensional picture using depth-buffering to eliminate the hidden surfaces. Suppose further that one part of your picture is composed of coplanar figures A and B, where B is a sort of decal that should always appear on top of figure A.

Your first approach might be to draw B after you've drawn A, setting the depth-buffering function to replace on greater or equal. Due to the finite precision of the floating-point representations of the vertices, however, round-off error can cause polygon B to be sometimes a bit in front and sometimes a bit behind figure A. Here's one solution to this problem.

Disable the depth buffer for writing, and render A.

Enable the depth buffer for writing, and render B.

Disable the color buffer for writing, and render A again.

Enable the color buffer for writing.

Note that during the entire process, the depth-buffer test is enabled. In step 1, A is rendered wherever it should be, but none of the depth-buffer values are changed; thus, in step 2, wherever B appears over A, B is guaranteed to be drawn. Step 3 simply makes sure that all of the depth values under A are updated correctly, but since RGBA writes are disabled, the color pixels are unaffected. Finally, step 4 returns the system to the default state (writing is enabled both in the depth buffer and in the color buffer).

If a stencil buffer is available, the following simpler technique works.

Configure the stencil buffer to write one if the depth test passes, and zero otherwise. Render A.

Configure the stencil buffer to make no stencil value change, but to render only where stencil values are one. Disable the depth-buffer test and its update. Render B.

With this method, it's not necessary to initialize the contents of the stencil buffer at any time, because the stencil value of all pixels of interest (that is, those rendered by A) are set when A is rendered. Be sure to reenable the depth test and disable the stencil test before additional polygons are drawn. (See "Selecting Color Buffers for Writing and Reading," "Depth Test," and "Stencil Test" in Chapter 10.)

Drawing Filled, Concave Polygons Using the Stencil Buffer

Consider the concave polygon 1234567 shown in Figure 14-2. Imagine that it's drawn as a series of triangles: 123, 134, 145, 156, 167, all of which are shown in the figure. The heavier line represents the original polygon boundary. Drawing all these triangles divides the buffer into nine regions A, B, C, ..., I, where region I is outside all the triangles.

Figure 14-2 : Concave Polygon

In the text of the figure, each of the region names is followed by a list of the triangles that cover it. Regions A, D, and F make up the original polygon; note that these three regions are covered by an odd number of triangles. Every other region is covered by an even number of triangles (possibly zero). Thus, to render the inside of the concave polygon, you just need to render regions that are enclosed by an odd number of triangles. This can be done using the stencil buffer, with a two-pass algorithm.

First, clear the stencil buffer and disable writing into the color buffer. Next, draw each of the triangles in turn, using the GL_INVERT function in the stencil buffer. (For best performance, use triangle fans.) This flips the value between zero and a nonzero value every time a triangle is drawn that covers a pixel. After all the triangles are drawn, if a pixel is covered an even number of times, the value in the stencil buffers is zero; otherwise, it's nonzero. Finally, draw a large polygon over the whole region (or redraw the triangles), but allow drawing only where the stencil buffer is nonzero.

Note: There's a slight generalization of the preceding technique, where you don't need to start with a polygon vertex. In the 1234567 example, let P be any point on or off the polygon. Draw the triangles: P12, P23, P34, P45, P56, P67, and P71. Regions covered by an odd number of triangles are inside; other regions are outside. This is a generalization in that if P happens to be one of the polygon's edges, one of the triangles is empty.

This technique can be used to fill both nonsimple polygons (polygons whose edges cross each other) and polygons with holes. The following example illustrates how to handle a complicated polygon with two regions, one four-sided and one five-sided. Assume further that there's a triangular and a four-sided hole (it doesn't matter in which regions the holes lie). Let the two regions be abcd and efghi, and the holes jkl and mnop. Let z be any point on the plane. Draw the following triangles:

zab zbc zcd zda zef zfg zgh zhi zie zjk zkl zlj zmn zno zop zpm

Mark regions covered by an odd number of triangles as in, and those covered by an even number as out. (See "Stencil Test" in Chapter 10 for more information about the stencil buffer.)

Finding Interference Regions

If you're designing a mechanical part made from smaller three-dimensional pieces, you often want to display regions where the pieces overlap. In many cases, such regions indicate design errors where parts of a machine interfere with each other. In the case of moving parts, it can be even more valuable, since a search for interfering regions can be done through a complete mechanical cycle of the design. The method for doing this is complicated, and the description here might be too brief. Complete details can be found in the paper Interactive Inspection of Solids: Cross-sections and Interferences, by Jarek Rossignac, Abe Megahed, and Bengt-Olaf Schneider (SIGGRAPH 1992 Proceedings).

The method is related to the capping algorithm described in "Stencil Test" in Chapter 10. The idea is to pass an arbitrary clipping plane through the objects that you want to test for interference, and then determine when a portion of the clipping plane is inside more than one object at a time. For a static image, the clipping plane can be moved manually to highlight interfering regions; for a dynamic image, it might be easier to use a grid of clipping planes to search for all possible interferences.

Draw each of the objects you want to check and clip them against the clipping plane. Note which pixels are inside the object at that clipping plane using an odd-even count in the stencil buffer, as explained in the preceding section. (For properly formed objects, a point is inside the object if a ray drawn from that point to the eye intersects an odd number of surfaces of the object.) To find interferences, you need to find pixels in the framebuffer where the clipping plane is in the interior of two or more regions at once; in other words, in the intersection of the interiors of any pair of objects.

If multiple objects need to be tested for mutual intersection, store 1 bit every time some intersection appears, and another bit wherever the clipping buffer is inside any of the objects (the union of the objects' interiors). For each new object, determine its interior, find the intersection of that interior with the union of the interiors of the objects so far tested, and keep track of the intersection points. Then add the interior points of the new object to the union of the other objects' interiors.

You can perform the operations described in the preceding paragraph by using different bits in the stencil buffer together with various masking operations. Three bits of stencil buffer are required per pixel - one for the toggling to determine the interior of each object, one for the union of all interiors discovered so far, and one for the regions where interference has occurred so far. To make this discussion more concrete, assume the 1 bit of the stencil buffer is for toggling interior/exterior, the 2 bit is the running union, and the 4 bit is for interferences so far. For each object that you're going to render, clear the 1 bit (using a stencil mask of one and clearing to zero), then toggle the 1 bit by keeping the stencil mask as one and using the GL_INVERT stencil operation.

You can find intersections and unions of the bits in the stencil buffers using the stenciling operations. For example, to make bits in buffer 2 be the union of the bits in buffers 1 and 2, mask the stencil to those 2 bits, and draw something over the entire object with the stencil function set to pass if anything nonzero occurs. This happens if the bits in buffer 1, buffer 2, or both are turned on. If the comparison succeeds, write a 1 in buffer 2. Also, make sure that drawing in the color buffer is disabled. An intersection calculation is similar - set the function to pass only if the value in the two buffers is equal to 3 (bits turned on in both buffers 1 and 2). Write the result into the correct buffer. (See "Stencil Test" in Chapter 10.)

Shadows

Every possible projection of three-dimensional space to three-dimensional space can be achieved with a suitable 4 ´ 4 invertible matrix and homogeneous coordinates. If the matrix isn't invertible but has rank 3, it projects three-dimensional space onto a two-dimensional plane. Every such possible projection can be achieved with a suitable rank-3 4 ´ 4 matrix. To find the shadow of an arbitrary object on an arbitrary plane from an arbitrary light source (possibly at infinity), you need to find a matrix representing that projection, multiply it on the matrix stack, and draw the object in the shadow color. Keep in mind that you need to project onto each plane that you're calling the "ground."

As a simple illustration, assume the light is at the origin, and the equation of the ground plane is ax+by+c+d=0. Given a vertex S=(sx,sy,sz,1), the line from the light through S includes all points &agr; S, where &agr; is an arbitrary real number. The point where this line intersects the plane occurs when

&agr; (a*sz+b*sy+c*sz) + d = 0,

so

&agr; = - &dgr; /(a*sx+b*sy+c*sz).

Plugging this back into the line, we get

- &dgr; ( &sgr; &xgr; , &sgr; &psgr; , &sgr; &zgr; )/( &agr; * &sgr; &xgr; + &bgr; * &sgr; &psgr; + &khgr; * &sgr; &zgr; )

for the point of intersection.

The matrix that maps S to this point for every S is

This matrix can be used if you first translate the world so that the light is at the origin.

If the light is from an infinite source, all you have is a point S and a direction D = (dx,dy,dz). Points along the line are given by

S + &agr; D

Proceeding as before, the intersection of this line with the plane is given by

a(sx+ &agr; dx)+b(sy+ &agr; dy)+c(sz+ &agr; dz)+d = 0

Solving for &agr; , plugging that back into the equation for a line, and then determining a projection matrix gives

This matrix works given the plane and an arbitrary direction vector. There's no need to translate anything first. (See Chapter 3 and Appendix F.)

Hidden-Line Removal

If you want to draw a wireframe object with hidden lines removed, one approach is to draw the outlines using lines and then fill the interiors of the polygons making up the surface with polygons having the background color. With depth-buffering enabled, this interior fill covers any outlines that would be obscured by faces closer to the eye. This method would work, except that there's no guarantee that the interior of the object falls entirely inside the polygon's outline; in fact, it might overlap it in various places.

There's an easy, two-pass solution using either polygon offset or the stencil buffer. Polygon offset is usually the preferred technique, since polygon offset is almost always faster than stencil buffer. Both methods are described here, so you can see how both approaches to the problem work.

Hidden-Line Removal with Polygon Offset

To use polygon offset to accomplish hidden-line removal, the object is drawn twice. The highlighted edges are drawn in the foreground color, using filled polygons but with the polygon mode GL_LINE to rasterize it as a wireframe. Then the filled polygons are drawn with the default polygon mode, which fills the interior of the wireframe, and with enough polygon offset to nudge the filled polygons a little farther from the eye. With the polygon offset, the interior recedes just enough that the highlighted edges are drawn without unpleasant visual artifacts.

glEnable(GL_DEPTH_TEST);
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
set_color(foreground);
draw_object_with_filled_polygons();

glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glEnable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(1.0, 1.0);
set_color(background);
draw_object_with_filled_polygons();
glDisable(GL_POLYGON_OFFSET_FILL);

You may need to adjust the amount of offset needed (for wider lines, for example). (See "Polygon Offset" in Chapter 6 for more information.)

Hidden-Line Removal with the Stencil Buffer

Using the stencil buffer for hidden-line removal is a more complicated procedure. For each polygon, you'll need to clear the stencil buffer, and then draw the outline both in the framebuffer and in the stencil buffer. Then when you fill the interior, enable drawing only where the stencil buffer is still clear. To avoid doing an entire stencil-buffer clear for each polygon, an easy way to clear it is simply to draw 0's into the buffer using the same polygon outline. In this way, you need to clear the entire stencil buffer only once.

For example, the following code represents the inner loop you might use to perform such hidden-line removal. Each polygon is outlined in the foreground color, filled with the background color, and then outlined again in the foreground color. The stencil buffer is used to keep the fill color of each polygon from overwriting its outline. To optimize performance, the stencil and color parameters are changed only twice per loop by using the same values both times the polygon outline is drawn.

glEnable(GL_STENCIL_TEST);
glEnable(GL_DEPTH_TEST);
glClear(GL_STENCIL_BUFFER_BIT);
glStencilFunc(GL_ALWAYS, 0, 1);
glStencilOp(GL_INVERT, GL_INVERT, GL_INVERT);
set_color(foreground);
for (i=0; i < max; i++) {
    outline_polygon(i);
    set_color(background);
    glStencilFunc(GL_EQUAL, 0, 1);
    glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
    fill_polygon(i);
    set_color(foreground);
    glStencilFunc(GL_ALWAYS, 0, 1);
    glStencilOp(GL_INVERT, GL_INVERT, GL_INVERT);
    outline_polygon(i);
}

(See "Stencil Test" in Chapter 10.)

Texture-Mapping Applications

Texture mapping is quite powerful, and it can be used in some interesting ways. Here are a few advanced applications of texture mapping.

  • Antialiased text - Define a texture map for each character at a relatively high resolution, and then map them onto smaller areas using the filtering provided by texturing. This also makes text appear correctly on surfaces that aren't aligned with the screen, but are tilted and have some perspective distortion.
  • Antialiased lines - These can be done like antialiased text: Make the line in the texture several pixels wide, and use the texture filtering to antialias the lines.
  • Image scaling and rotation - If you put an image into a texture map and use that texture to map onto a polygon, rotating and scaling the polygon effectively rotates and scales the image.
  • Image warping - As in the preceding example, store the image as a texture map, but map it to some spline-defined surface (use evaluators). As you warp the surface, the image follows the warping.
  • Projecting images - Put the image in a texture map, and project it as a spotlight, creating a slide projector effect. (See "The q Coordinate" in Chapter 9 for more information about how to model a spotlight using textures.)

(See Chapter 3 for information about rotating and scaling, Chapter 9 for more information about creating textures, and Chapter 12 for details on evaluators.)

Drawing Depth-Buffered Images

For complex static backgrounds, the rendering time for the geometric description of the background can be greater than the time it takes to draw a pixel image of the rendered background. If there's a fixed background and a relatively simple changing foreground, you may want to draw the background and its associated depth-buffered version as an image rather than render it geometrically. The foreground might also consist of items that are time-consuming to render, but whose framebuffer images and depth buffers are available. You can render these items into a depth-buffered environment using a two-pass algorithm.

For example, if you're drawing a model of a molecule made of spheres, you might have an image of a beautifully rendered sphere and its associated depth-buffer values that were calculated using Phong shading or ray-tracing or by using some other scheme that isn't directly available through OpenGL. To draw a complex model, you might be required to draw hundreds of such spheres, which should be depth-buffered together.

To add a depth-buffered image to the scene, first draw the image's depth-buffer values into the depth buffer using glDrawPixels(). Then enable depth-buffering, set the writemask to zero so that no drawing occurs, and enable stenciling such that the stencil buffers get drawn whenever a write to the depth buffer occurs.

Then draw the image into the color buffer, masked by the stencil buffer you've just written so that writing occurs only when there's a 1 in the stencil buffer. During this write, set the stenciling function to zero out the stencil buffer so that it's automatically cleared when it's time to add the next image to the scene. If the objects are to be moved nearer to or farther from the viewer, you need to use an orthographic projection; in these cases, you use GL_DEPTH_BIAS with glPixelTransfer*() to move the depth image. (See "Coordinate System Survival Kit" in Chapter 2, "Depth Test" and "Stencil Test" in Chapter 10, and Chapter 8 for details on glDrawPixels() and glPixelTransfer*().)

Dirichlet Domains

Given a set S of points on a plane, the Dirichlet domain or Voronoi polygon of one of the points is the set of all points in the plane closer to that point than to any other point in the set S. These points provide the solution to many problems in computational geometry. Figure 14-3 shows outlines of the Dirichlet domains for a set of points.

Figure 14-3 : Dirichlet Domains

If you draw a depth-buffered cone with its apex at the point in a different color than each of the points in S, the Dirichlet domain for each point is drawn in that color. The easiest way to do this is to precompute a cone's depth in an image and use the image as the depth-buffer values as described in the preceding section. You don't need an image to draw in the framebuffer as in the case of shaded spheres, however. While you're drawing into the depth buffer, use the stencil buffer to record the pixels where drawing should occur by first clearing it and then writing nonzero values wherever the depth test succeeds. To draw the Dirichlet region, draw a polygon over the entire window, but enable drawing only where the stencil buffers are nonzero.

You can do this perhaps more easily by rendering cones of uniform color with a simple depth buffer, but a good cone might require thousands of polygons. The technique described in this section can render much higher-quality cones much more quickly. (See "A Hidden-Surface Removal Survival Kit" in Chapter 5 and "Depth Test" in Chapter 10.)

Life in the Stencil Buffer

The Game of Life, invented by John Conway, is played on a rectangular grid where each grid location is "alive" or "dead." To calculate the next generation from the current one, count the number of live neighbors for each grid location (the eight adjacent grid locations are neighbors). A grid location is alive in generation n+1 if it was alive in generation n and has exactly two or three live neighbors, or if it was dead in generation n and has exactly three live neighbors. In all other cases, it is dead in generation n+1. This game generates some incredibly interesting patterns given different initial configurations. (See Martin Gardner, "Mathematical Games," Scientific American, vol. 223, no. 4, October 1970, p. 120123.) Figure 14-4 shows six generations from a game.

Figure 14-4 : Six Generations from the Game of Life

One way to create this game using OpenGL is to use a multipass algorithm. Keep the data in the color buffer, one pixel for each grid point. Assume that black (all zeros) is the background color, and the color of a live pixel is nonzero. Initialize by clearing the depth and stencil buffers to zero, set the depth-buffer writemask to zero, and set the depth comparison function so that it passes on not-equal. To iterate, read the image off the screen, enable drawing into the depth buffer, and set the stencil function so that it increments whenever a depth comparison succeeds but leaves the stencil buffer unchanged otherwise. Disable drawing into the color buffer.

Next, draw the image eight times, offset one pixel in each vertical, horizontal, and diagonal direction. When you're done, the stencil buffer contains a count of the number of live neighbors for each pixel. Enable drawing to the color buffer, set the color to the color for live cells, and set the stencil function to draw only if the value in the stencil buffer is 3 (three live neighbors). In addition, if this drawing occurs, decrement the value in the stencil buffer. Then draw a rectangle covering the image; this paints each cell that has exactly three live neighbors with the "alive" color.

At this point, the stencil buffers contain 0, 1, 2, 4, 5, 6, 7, 8, and the values under the 2's are correct. The values under 0, 1, 4, 5, 6, 7, and 8 must be cleared to the "dead" color. Set the stencil function to draw whenever the value is not 2, and to zero the stencil values in all cases. Then draw a large polygon of the "dead" color across the entire image. You're done.

For a usable demonstration program, you might want to zoom the grid up to a size larger than a single pixel; it's hard to see detailed patterns with a single pixel per grid point. (See "Coordinate System Survival Kit" in Chapter 2, and "Depth Test" and "Stencil Test" in Chapter 10.)

Alternative Uses for glDrawPixels() and glCopyPixels()

You might think of glDrawPixels() as a way to draw a rectangular region of pixels to the screen. Although this is often what it's used for, some other interesting uses are outlined here.

  • Video - Even if your machine doesn't have special video hardware, you can display short movie clips by repeatedly drawing frames with glDrawPixels() in the same region of the back buffer and then swapping the buffers. The size of the frames you can display with reasonable performance using this method depends on your hardware's drawing speed, so you might be limited to 100 ´ 100 pixel movies (or smaller) if you want smooth fake video.
  • Airbrush - In a paint program, your airbrush (or paintbrush) shape can be simulated using alpha values. The color of the paint is represented as the color values. To paint with a circular brush in blue, repeatedly draw a blue square with glDrawPixels() where the alpha values are largest in the center and taper to zero at the edges of a circle centered in the square. Draw using a blending function that uses alpha of the incoming color and (1-alpha) of the color already at the pixel. If the alpha values in the brush are all much less than one, you have to paint over an area repeatedly to get a solid color. If the alpha values are near one, each brush stroke pretty much obliterates the colors underneath.
  • Filtered Zooms - If you zoom a pixel image by a nonintegral amount, OpenGL effectively uses a box filter, which can lead to rather severe aliasing effects. To improve the filtering, jitter the resulting image by amounts less than a pixel and redraw it multiple times, using alpha blending to average the resulting pixels. The result is a filtered zoom.
  • Transposing Images - You can swap same-size images in place with glCopyPixels() using the XOR operation. With this method, you can avoid having to read the images back into processor memory. If A and B represent the two images, the operation looks like this:

A = A XOR B

B = A XOR B

A = A XOR B


Appendix A - OpenGL Programming Guide

OpenGL Programming Guide
Programming Guide > Appendix a

Appendix A
Order of Operations

This book describes all the operations performed between when vertices are initially specified and fragments are finally written into the framebuffer. The chapters of this book are arranged in an order that facilitates learning rather than in the exact order in which these operations are actually performed. Sometimes the exact order of operations doesn't matter - for example, surfaces can be converted to polygons and then transformed, or transformed first and then converted to polygons, with identical results - and different implementations of OpenGL might do things differently.

This appendix describes a possible order; any implementation is required to give equivalent results. If you want more details than are presented here, see the OpenGL Reference Manual.

This appendix has the following major sections:

  • "Overview"
  • "Geometric Operations"
  • "Pixel Operations"
  • "Fragment Operations"
  • "Odds and Ends"

Overview

This section gives an overview of the order of operations, as shown in Figure A-1. Geometric data (vertices, lines, and polygons) follows the path through the row of boxes that include evaluators and per-vertex operations, while pixel data (pixels, images, and bitmaps) is treated differently for part of the process. Both types of data undergo the rasterization and per-fragment operations before the final pixel data is written into the framebuffer.

Figure A-1 : Order of Operations

All data, whether it describes geometry or pixels, can be saved in a display list or processed immediately. When a display list is executed, the data is sent from the display list just as if it were sent by the application.

All geometric primitives are eventually described by vertices. If evaluators are used, that data is converted to vertices and treated as vertices from then on. Vertex data may also be stored in and used from specialized vertex arrays. Per-vertex calculations are performed on each vertex, followed by rasterization to fragments. For pixel data, pixel operations are performed, and the results are either stored in the texture memory, used for polygon stippling, or rasterized to fragments.

Finally, the fragments are subjected to a series of per-fragment operations, after which the final pixel values are drawn into the framebuffer.

Geometric Operations

Geometric data, whether it comes from a display list, an evaluator, the vertices of a rectangle, or as raw data, consists of a set of vertices and the type of primitive it describes (a vertex, line, or polygon). Vertex data includes not only the (x, y, z, w) coordinates, but also a normal vector, texture coordinates, a RGBA color, a color index, material properties, and edge-flag data. All these elements except the vertex's coordinates can be specified in any order, and default values exist as well. As soon as the vertex command glVertex*() is issued, the components are padded, if necessary, to four dimensions (using z = 0 and w = 1), and the current values of all the elements are associated with the vertex. The complete set of vertex data is then processed. (If vertex arrays are used, vertex data may be batch processed and processed vertices may be reused.)

Per-Vertex Operations

In the per-vertex operations stage of processing, each vertex's spatial coordinates are transformed by the modelview matrix, while the normal vector is transformed by that matrix's inverse transpose and renormalized if specified. If automatic texture generation is enabled, new texture coordinates are generated from the transformed vertex coordinates, and they replace the vertex's old texture coordinates. The texture coordinates are then transformed by the current texture matrix and passed on to the primitive assembly step.

Meanwhile, the lighting calculations, if enabled, are performed using the transformed vertex and normal vector coordinates, and the current material, lights, and lighting model. These calculations generate new colors or indices that are clamped or masked to the appropriate range and passed on to the primitive assembly step.

Primitive Assembly

Primitive assembly differs, depending on whether the primitive is a point, a line, or a polygon. If flat shading is enabled, the colors or indices of all the vertices in a line or polygon are set to the same value. If special clipping planes are defined and enabled, they're used to clip primitives of all three types. (The clipping-plane equations are transformed by the inverse transpose of the modelview matrix when they're specified.) Point clipping simply passes or rejects vertices; line or polygon clipping can add additional vertices depending on how the line or polygon is clipped. After this clipping, the spatial coordinates of each vertex are transformed by the projection matrix, and the results are clipped against the standard viewing planes x = ± &ohgr; , y = ± &ohgr; , and z = ± &ohgr; .

If selection is enabled, any primitive not eliminated by clipping generates a selection-hit report, and no further processing is performed. Without selection, perspective division by w occurs and the viewport and depth-range operations are applied. Also, if the primitive is a polygon, it's then subjected to a culling test (if culling is enabled). A polygon might convert to vertices or lines, depending on the polygon mode.

Finally, points, lines, and polygons are rasterized to fragments, taking into account polygon or line stipples, line width, and point size. Rasterization involves determining which squares of an integer grid in window coordinates are occupied by the primitive. If antialiasing is enabled, coverage (the portion of the square that is occupied by the primitive) is also computed. Color and depth values are also assigned to each such square. If polygon offset is enabled, depth values are slightly modified by a calculated offset value.

Pixel Operations

Pixels from host memory are first unpacked into the proper number of components. The OpenGL unpacking facility handles a number of different formats. Next, the data is scaled, biased, and processed using a pixel map. The results are clamped to an appropriate range depending on the data type and then either written in the texture memory for use in texture mapping or rasterized to fragments.

If pixel data is read from the framebuffer, pixel-transfer operations (scale, bias, mapping, and clamping) are performed. The results are packed into an appropriate format and then returned to processor memory.

The pixel copy operation is similar to a combination of the unpacking and transfer operations, except that packing and unpacking is unnecessary, and only a single pass is made through the transfer operations before the data is written back into the framebuffer.

Texture Memory

OpenGL Version 1.1 provides additional control over texture memory. Texture image data can be specified from framebuffer memory, as well as processor memory. All or a portion of a texture image may be replaced. Texture data may be stored in texture objects, which can be loaded into texture memory. If there are too many texture objects to fit into texture memory at the same time, the textures that have the highest priorities remain in the texture memory.

Fragment Operations

If texturing is enabled, a texel is generated from texture memory for each fragment and applied to the fragment. Then fog calculations are performed, if they're enabled, followed by the application of coverage (antialiasing) values, if antialiasing is enabled.

Next comes scissoring, followed by the alpha test (in RGBA mode only), the stencil test, and the depth-buffer test. If in RGBA mode, blending is performed. Blending is followed by dithering and logical operation. All these operations may be disabled.

The fragment is then masked by a color mask or an index mask, depending on the mode, and drawn into the appropriate buffer. If fragments are being written into the stencil or depth buffer, masking occurs after the stencil and depth tests, and the results are drawn into the framebuffer without performing the blending, dithering, or logical operation.

Odds and Ends

Matrix operations deal with the current matrix stack, which can be the modelview, the projection, or the texture matrix stack. The commands glMultMatrix*(), glLoadMatrix*(), and glLoadIdentity() are applied to the top matrix on the stack, while glTranslate*(), glRotate*(), glScale*(), glOrtho(), and glFrustum() are used to create a matrix that's multiplied by the top matrix. When the modelview matrix is modified, its inverse transpose is also generated for normal vector transformation.

The commands that set the current raster position are treated exactly like a vertex command up until when rasterization would occur. At this point, the value is saved and is used in the rasterization of pixel data.

The various glClear() commands bypass all operations except scissoring, dithering, and writemasking.


Appendix B - OpenGL Programming Guide

OpenGL Programming Guide
Programming Guide > Appendix b

Appendix B
State Variables

This appendix lists the queryable OpenGL state variables, their default values, and the commands for obtaining the values of these variables. The OpenGL Reference Manual contains detailed information on all the commands and constants discussed in this appendix. This appendix has these major sections:

  • "The Query Commands"
  • "OpenGL State Variables"

The Query Commands

In addition to the basic commands to obtain the values of simple state variables (commands such as glGetIntegerv() and glIsEnabled(), which are described in "Basic State Management" in Chapter 2), there are other specialized commands to return more complex state variables. The prototypes for these specialized commands are listed here. Some of these routines, such as glGetError() and glGetString(), have been discussed in more detail elsewhere in the book.

To find out when you need to use these commands and their corresponding symbolic constants, use the tables in the next section, "OpenGL State Variables." Also see the OpenGL Reference Manual.

void glGetClipPlane(GLenum plane, GLdouble *equation);

GLenum glGetError(void);

void glGetLight{if}v(GLenum light, GLenum pname, TYPE *params);

void glGetMap{ifd}v(GLenum target, GLenum query, TYPE *v);

void glGetMaterial{if}v(GLenum face, GLenum pname, TYPE *params);

void glGetPixelMap{f ui us}v(GLenum map, TYPE *values);

void glGetPolygonStipple(GLubyte *mask);

const GLubyte * glGetString(GLenum name);

void glGetTexEnv{if}v(GLenum target, GLenum pname, TYPE *params);

void glGetTexGen{ifd}v(GLenum coord, GLenum pname, TYPE *params);

void glGetTexImage(GLenum target, GLint level, GLenum format,
GLenum type, GLvoid *pixels);

void glGetTexLevelParameter{if}v(GLenum target, GLint level,
GLenum pname, TYPE *params);

void glGetTexParameter{if}v(GLenum target, GLenum pname,
TYPE *params);

void gluGetNurbsProperty(GLUnurbsObj *nobj, GLenum property,
GLfloat *value);

const GLubyte * gluGetString(GLenum name);

void gluGetTessProperty(GLUtesselator *tess, GLenum which,
GLdouble *data);

OpenGL State Variables

The following pages contain tables that list the names of queryable state variables. For each variable, the tables list a description of it, its attribute group, its initial or minimum value, and the suggested glGet*() command to use for obtaining it. State variables that can be obtained using glGetBooleanv(), glGetIntegerv(), glGetFloatv(), or glGetDoublev() are listed with just one of these commands - the one that's most appropriate given the type of data to be returned. (Some vertex array variables can be queried only with glGetPointerv().) These state variables can't be obtained using glIsEnabled(). However, state variables for which glIsEnabled() is listed as the query command can also be obtained using glGetBooleanv(), glGetIntegerv(), glGetFloatv(), and glGetDoublev(). State variables for which any other command is listed as the query command can be obtained only by using that command.

If one or more attribute groups are listed, the state variable belongs to the listed group or groups. If no attribute group is listed, the variable doesn't belong to any group. glPushAttrib(), glPushClientAttrib(), glPopAttrib(), and glPopClientAttrib() may be used to save and restore all state values that belong to an attribute group. (See "Attribute Groups" in Chapter 2 for more information.)

All queryable state variables, except the implementation-dependent ones, have initial values. If no initial value is listed, you need to consult either the section where that variable is discussed or the OpenGL Reference Manual to determine its initial value.

Current Values and Associated Data

Table B-1 : State Variables for Current Values and Associated Data

State Variable | Description | Attribute Group | Initial Value | Get Command
GL_CURRENT_COLOR | Current color | current | 1, 1, 1, 1 | glGetIntegerv(), glGetFloatv()
GL_CURRENT_INDEX | Current color index | current | 1 | glGetIntegerv(), glGetFloatv()
GL_CURRENT_TEXTURE_COORDS | Current texture coordinates | current | 0, 0, 0, 1 | glGetFloatv()
GL_CURRENT_NORMAL | Current normal | current | 0, 0, 1 | glGetFloatv()
GL_CURRENT_RASTER_POSITION | Current raster position | current | 0, 0, 0, 1 | glGetFloatv()
GL_CURRENT_RASTER_DISTANCE | Current raster distance | current | 0 | glGetFloatv()
GL_CURRENT_RASTER_COLOR | Color associated with raster position | current | 1, 1, 1, 1 | glGetIntegerv(), glGetFloatv()
GL_CURRENT_RASTER_INDEX | Color index associated with raster position | current | 1 | glGetIntegerv(), glGetFloatv()
GL_CURRENT_RASTER_TEXTURE_COORDS | Texture coordinates associated with raster position | current | 0, 0, 0, 1 | glGetFloatv()
GL_CURRENT_RASTER_POSITION_VALID | Raster position valid bit | current | GL_TRUE | glGetBooleanv()
GL_EDGE_FLAG | Edge flag | current | GL_TRUE | glGetBooleanv()

Vertex Array

Table B-2 : (continued) Vertex Array State Variables

State Variable | Description | Attribute Group | Initial Value | Get Command
GL_VERTEX_ARRAY | Vertex array enable | vertex-array | GL_FALSE | glIsEnabled()
GL_VERTEX_ARRAY_SIZE | Coordinates per vertex | vertex-array | 4 | glGetIntegerv()
GL_VERTEX_ARRAY_TYPE | Type of vertex coordinates | vertex-array | GL_FLOAT | glGetIntegerv()
GL_VERTEX_ARRAY_STRIDE | Stride between vertices | vertex-array | 0 | glGetIntegerv()
GL_VERTEX_ARRAY_POINTER | Pointer to the vertex array | vertex-array | NULL | glGetPointerv()
GL_NORMAL_ARRAY | Normal array enable | vertex-array | GL_FALSE | glIsEnabled()
GL_NORMAL_ARRAY_TYPE | Type of normal coordinates | vertex-array | GL_FLOAT | glGetIntegerv()
GL_NORMAL_ARRAY_STRIDE | Stride between normals | vertex-array | 0 | glGetIntegerv()
GL_NORMAL_ARRAY_POINTER | Pointer to the normal array | vertex-array | NULL | glGetPointerv()
GL_COLOR_ARRAY | RGBA color array enable | vertex-array | GL_FALSE | glIsEnabled()
GL_COLOR_ARRAY_SIZE | Colors per vertex | vertex-array | 4 | glGetIntegerv()
GL_COLOR_ARRAY_TYPE | Type of color components | vertex-array | GL_FLOAT | glGetIntegerv()
GL_COLOR_ARRAY_STRIDE | Stride between colors | vertex-array | 0 | glGetIntegerv()
GL_COLOR_ARRAY_POINTER | Pointer to the color array | vertex-array | NULL | glGetPointerv()
GL_INDEX_ARRAY | Color-index array enable | vertex-array | GL_FALSE | glIsEnabled()
GL_INDEX_ARRAY_TYPE | Type of color indices | vertex-array | GL_FLOAT | glGetIntegerv()
GL_INDEX_ARRAY_STRIDE | Stride between color indices | vertex-array | 0 | glGetIntegerv()
GL_INDEX_ARRAY_POINTER | Pointer to the index array | vertex-array | NULL | glGetPointerv()
GL_TEXTURE_COORD_ARRAY | Texture coordinate array enable | vertex-array | GL_FALSE | glIsEnabled()
GL_TEXTURE_COORD_ARRAY_SIZE | Texture coordinates per element | vertex-array | 4 | glGetIntegerv()
GL_TEXTURE_COORD_ARRAY_TYPE | Type of texture coordinates | vertex-array | GL_FLOAT | glGetIntegerv()
GL_TEXTURE_COORD_ARRAY_STRIDE | Stride between texture coordinates | vertex-array | 0 | glGetIntegerv()
GL_TEXTURE_COORD_ARRAY_POINTER | Pointer to the texture coordinate array | vertex-array | NULL | glGetPointerv()
GL_EDGE_FLAG_ARRAY | Edge flag array enable | vertex-array | GL_FALSE | glIsEnabled()
GL_EDGE_FLAG_ARRAY_STRIDE | Stride between edge flags | vertex-array | 0 | glGetIntegerv()
GL_EDGE_FLAG_ARRAY_POINTER | Pointer to the edge flag array | vertex-array | NULL | glGetPointerv()

Transformation

Table B-3 : Transformation State Variables

State Variable | Description | Attribute Group | Initial Value | Get Command
GL_MODELVIEW_MATRIX | Modelview matrix stack | - | Identity | glGetFloatv()
GL_PROJECTION_MATRIX | Projection matrix stack | - | Identity | glGetFloatv()
GL_TEXTURE_MATRIX | Texture matrix stack | - | Identity | glGetFloatv()
GL_VIEWPORT | Viewport origin and extent | viewport | - | glGetIntegerv()
GL_DEPTH_RANGE | Depth range near and far | viewport | 0, 1 | glGetFloatv()
GL_MODELVIEW_STACK_DEPTH | Modelview matrix stack pointer | - | 1 | glGetIntegerv()
GL_PROJECTION_STACK_DEPTH | Projection matrix stack pointer | - | 1 | glGetIntegerv()
GL_TEXTURE_STACK_DEPTH | Texture matrix stack pointer | - | 1 | glGetIntegerv()
GL_MATRIX_MODE | Current matrix mode | transform | GL_MODELVIEW | glGetIntegerv()
GL_NORMALIZE | Current normal normalization on/off | transform/ enable | GL_FALSE | glIsEnabled()
GL_CLIP_PLANEi | User clipping plane coefficients | transform | 0, 0, 0, 0 | glGetClipPlane()
GL_CLIP_PLANEi | ith user clipping plane enabled | transform/ enable | GL_FALSE | glIsEnabled()

Coloring

Table B-4 : Coloring State Variables

State Variable | Description | Attribute Group | Initial Value | Get Command
GL_FOG_COLOR | Fog color | fog | 0, 0, 0, 0 | glGetFloatv()
GL_FOG_INDEX | Fog index | fog | 0 | glGetFloatv()
GL_FOG_DENSITY | Exponential fog density | fog | 1.0 | glGetFloatv()
GL_FOG_START | Linear fog start | fog | 0.0 | glGetFloatv()
GL_FOG_END | Linear fog end | fog | 1.0 | glGetFloatv()
GL_FOG_MODE | Fog mode | fog | GL_EXP | glGetIntegerv()
GL_FOG | True if fog enabled | fog/enable | GL_FALSE | glIsEnabled()
GL_SHADE_MODEL | glShadeModel() setting | lighting | GL_SMOOTH | glGetIntegerv()

Lighting

See also Table 5-1 and Table 5-3 for initial values.

Table B-5 : (continued) Lighting State Variables

State Variable | Description | Attribute Group | Initial Value | Get Command
GL_LIGHTING | True if lighting is enabled | lighting/e nable | GL_FALSE | glIsEnabled()
GL_COLOR_MATERIAL | True if color tracking is enabled | lighting | GL_FALSE | glIsEnabled()
GL_COLOR_MATERIAL_PARAMETER | Material properties tracking current color | lighting | GL_AMBIENT_ AND_DIFFUSE | glGetIntegerv()
GL_COLOR_MATERIAL_FACE | Face(s) affected by color tracking | lighting | GL_FRONT_ AND_BACK | glGetIntegerv()
GL_AMBIENT | Ambient material color | lighting | (0.2, 0.2, 0.2, 1.0) | glGetMaterialfv()
GL_DIFFUSE | Diffuse material color | lighting | (0.8, 0.8, 0.8, 1.0) | glGetMaterialfv()
GL_SPECULAR | Specular material color | lighting | (0.0, 0.0, 0.0, 1.0) | glGetMaterialfv()
GL_EMISSION | Emissive material color | lighting | (0.0, 0.0, 0.0, 1.0) | glGetMaterialfv()
GL_SHININESS | Specular exponent of material | lighting | 0.0 | glGetMaterialfv()
GL_LIGHT_MODEL_AMBIENT | Ambient scene color | lighting | (0.2, 0.2, 0.2, 1.0) | glGetFloatv()
GL_LIGHT_MODEL_LOCAL_VIEWER | Viewer is local | lighting | GL_FALSE | glGetBooleanv()
GL_LIGHT_MODEL_TWO_SIDE | Use two-sided lighting | lighting | GL_FALSE | glGetBooleanv()
GL_AMBIENT | Ambient intensity of light i | lighting | (0.0,0.0,0.0,1.0) | glGetLightfv()
GL_DIFFUSE | Diffuse intensity of light i | lighting | - | glGetLightfv()
GL_SPECULAR | Specular intensity of light i | lighting | - | glGetLightfv()
GL_POSITION | Position of light i | lighting | (0.0, 0.0, 1.0, 0.0) | glGetLightfv()
GL_CONSTANT_ATTENUATION | Constant attenuation factor | lighting | 1.0 | glGetLightfv()
GL_LINEAR_ATTENUATION | Linear attenuation factor | lighting | 0.0 | glGetLightfv()
GL_QUADRATIC_ATTENUATION | Quadratic attenuation factor | lighting | 0.0 | glGetLightfv()
GL_SPOT_DIRECTION | Spotlight direction of light i | lighting | (0.0, 0.0, -1.0) | glGetLightfv()
GL_SPOT_EXPONENT | Spotlight exponent of light i | lighting | 0.0 | glGetLightfv()
GL_SPOT_CUTOFF | Spotlight angle of light i | lighting | 180.0 | glGetLightfv()
GL_LIGHTi | True if light i enabled | lighting/e nable | GL_FALSE | glIsEnabled()
GL_COLOR_INDEXES | ca, cd, and cs for color-index lighting | lighting/e nable | 0, 1, 1 | glGetMaterialfv()

Rasterization

Table B-6 : (continued) Rasterization State Variables

State Variable | Description | Attribute Group | Initial Value | Get Command
GL_POINT_SIZE | Point size | point | 1.0 | glGetFloatv()
GL_POINT_SMOOTH | Point antialiasing on | point/enable | GL_FALSE | glIsEnabled()
GL_LINE_WIDTH | Line width | line | 1.0 | glGetFloatv()
GL_LINE_SMOOTH | Line antialiasing on | line/enable | GL_FALSE | glIsEnabled()
GL_LINE_STIPPLE_PATTERN | Line stipple | line | 1's | glGetIntegerv()
GL_LINE_STIPPLE_REPEAT | Line stipple repeat | line | 1 | glGetIntegerv()
GL_LINE_STIPPLE | Line stipple enable | line/enable | GL_FALSE | glIsEnabled()
GL_CULL_FACE | Polygon culling enabled | polygon/enable | GL_FALSE | glIsEnabled()
GL_CULL_FACE_MODE | Cull front-/back-facing polygons | polygon | GL_BACK | glGetIntegerv()
GL_FRONT_FACE | Polygon front-face CW/CCW indicator | polygon | GL_CCW | glGetIntegerv()
GL_POLYGON_SMOOTH | Polygon antialiasing on | polygon/enable | GL_FALSE | glIsEnabled()
GL_POLYGON_MODE | Polygon rasterization mode (front and back) | polygon | GL_FILL | glGetIntegerv()
GL_POLYGON_OFFSET_FACTOR | Polygon offset factor | polygon | 0 | glGetFloatv()
GL_POLYGON_OFFSET_BIAS | Polygon offset bias | polygon | 0 | glGetFloatv()
GL_POLYGON_OFFSET_POINT | Polygon offset enable for GL_POINT mode rasterization | polygon/enable | GL_FALSE | glIsEnabled()
GL_POLYGON_OFFSET_LINE | Polygon offset enable for GL_LINE mode rasterization | polygon/enable | GL_FALSE | glIsEnabled()
GL_POLYGON_OFFSET_FILL | Polygon offset enable for GL_FILL mode rasterization | polygon/enable | GL_FALSE | glIsEnabled()
GL_POLYGON_STIPPLE | Polygon stipple enable | polygon/enable | GL_FALSE | glIsEnabled()
- | Polygon stipple pattern | polygon-stipple | 1's | glGetPolygon- Stipple()

Texturing

Table B-7 : (continued) Texturing State Variables

State Variable | Description | Attribute Group | Initial Value | Get Command
GL_TEXTURE_x | True if x-D texturing enabled (x is 1D or 2D) | texture/e nable | GL_FALSE | glIsEnabled()
GL_TEXTURE_BINDING_x | Texture object bound to GL_TEXTURE_x (x is 1D or 2D) | texture | GL_FALSE | glGetIntegerv()
GL_TEXTURE | x-D texture image at level of detail i | - | - | glGetTexImage()
GL_TEXTURE_WIDTH | x-D texture image i's width | - | 0 | glGetTexLevelParameter*()
GL_TEXTURE_HEIGHT | x-D texture image i's height | - | 0 | glGetTexLevelParameter*()
GL_TEXTURE_BORDER | x-D texture image i's border width | - | 0 | glGetTexLevelParameter*()
GL_TEXTURE_INTERNAL _FORMAT | x-D texture image i's internal image format | - | 1 | glGetTexLevelParameter*()
GL_TEXTURE_RED_SIZE | x-D texture image i's red resolution | - | 0 | glGetTexLevelParameter*()
GL_TEXTURE_GREEN_SIZE | x-D texture image i's green resolution | - | 0 | glGetTexLevelParameter*()
GL_TEXTURE_BLUE_SIZE | x-D texture image i's blue resolution | - | 0 | glGetTexLevelParameter*()
GL_TEXTURE_ALPHA_SIZE | x-D texture image i's alpha resolution | - | 0 | glGetTexLevelParameter*()
GL_TEXTURE_LUMINANCE_SIZE | x-D texture image i's luminance resolution | - | 0 | glGetTexLevelParameter*()
GL_TEXTURE_INTENSITY_SIZE | x-D texture image i's intensity resolution | - | 0 | glGetTexLevelParameter*()
GL_TEXTURE_BORDER_COLOR | Texture border color | texture | 0, 0, 0, 0 | glGetTexParameter*()
GL_TEXTURE_MIN_FILTER | Texture minification function | texture | GL_ NEAREST_ MIPMAP_ LINEAR | glGetTexParameter*()
GL_TEXTURE_MAG_FILTER | Texture magnification function | texture | GL_LINEAR | glGetTexParameter*()
GL_TEXTURE_WRAP_x | Texture wrap mode (x is S or T) | texture | GL_REPEAT | glGetTexParameter*()
GL_TEXTURE_PRIORITY | Texture object priority | texture | 1 | glGetTexParameter*()
GL_TEXTURE_RESIDENCY | Texture residency | texture | GL_FALSE | glGetTexParameteriv()
GL_TEXTURE_ENV_MODE | Texture application function | texture | GL_ MODULATE | glGetTexEnviv()
GL_TEXTURE_ENV_COLOR | Texture environment color | texture | 0, 0, 0, 0 | glGetTexEnvfv()
GL_TEXTURE_GEN_x | Texgen enabled (x is S, T, R, or Q) | texture/e nable | GL_FALSE | glIsEnabled()
GL_EYE_PLANE | Texgen plane equation coefficients | texture | - | glGetTexGenfv()
GL_OBJECT_PLANE | Texgen object linear coefficients | texture | - | glGetTexGenfv()
GL_TEXTURE_GEN_MODE | Function used for texgen | texture | GL_EYE_ LINEAR | glGetTexGeniv()

Pixel Operations

Table B-8 : (continued) Pixel Operations

State Variable | Description | Attribute Group | Initial Value | Get Command
GL_SCISSOR_TEST | Scissoring enabled | scissor/enable | GL_FALSE | glIsEnabled()
GL_SCISSOR_BOX | Scissor box | scissor | - | glGetIntegerv()
GL_ALPHA_TEST | Alpha test enabled | color-buffer/ enable | GL_FALSE | glIsEnabled()
GL_ALPHA_TEST_FUNC | Alpha test function | color-buffer | GL_ALWAYS | glGetIntegerv()
GL_ALPHA_TEST_REF | Alpha test reference value | color-buffer | 0 | glGetIntegerv()
GL_STENCIL_TEST | Stenciling enabled | stencil-buffer/enable | GL_FALSE | glIsEnabled()
GL_STENCIL_FUNC | Stencil function | stencil-buffer | GL_ALWAYS | glGetIntegerv()
GL_STENCIL_VALUE_MASK | Stencil mask | stencil-buffer | 1's | glGetIntegerv()
GL_STENCIL_REF | Stencil reference value | stencil-buffer | 0 | glGetIntegerv()
GL_STENCIL_FAIL | Stencil fail action | stencil-buffer | GL_KEEP | glGetIntegerv()
GL_STENCIL_PASS_DEPTH_FAIL | Stencil depth buffer fail action | stencil-buffer | GL_KEEP | glGetIntegerv()
GL_STENCIL_PASS_DEPTH_PASS | Stencil depth buffer pass action | stencil-buffer | GL_KEEP | glGetIntegerv()
GL_DEPTH_TEST | Depth buffer enabled | depth-buffer/ena ble | GL_FALSE | glIsEnabled()
GL_DEPTH_FUNC | Depth buffer test function | depth-buffer | GL_LESS | glGetIntegerv()
GL_BLEND | Blending enabled | color-buffer/ enable | GL_FALSE | glIsEnabled()
GL_BLEND_SRC | Blending source function | color-buffer | GL_ONE | glGetIntegerv()
GL_BLEND_DST | Blending destination function | color-buffer | GL_ZERO | glGetIntegerv()
GL_DITHER | Dithering enabled | color-buffer/ enable | GL_TRUE | glIsEnabled()
GL_INDEX_LOGIC_OP | Color index logical operation enabled | color-buffer/ enable | GL_FALSE | glIsEnabled()
GL_COLOR_LOGIC_OP | RGBA color logical operation enabled | color-buffer/ enable | GL_FALSE | glIsEnabled()
GL_LOGIC_OP_MODE | Logical operation function | color-buffer | GL_COPY | glGetIntegerv()

Framebuffer Control

Table B-9 : Framebuffer Control State Variables

State Variable | Description | Attribute Group | Initial Value | Get Command
GL_DRAW_BUFFER | Buffers selected for drawing | color-buffer | - | glGetIntegerv()
GL_INDEX_WRITEMASK | Color-index writemask | color-buffer | 1's | glGetIntegerv()
GL_COLOR_WRITEMASK | Color write enables; R, G, B, or A | color-buffer | GL_TRUE | glGetBooleanv()
GL_DEPTH_WRITEMASK | Depth buffer enabled for writing | depth-buffer | GL_TRUE | glGetBooleanv()
GL_STENCIL_WRITEMASK | Stencil-buffer writemask | stencil-buffer | 1's | glGetIntegerv()
GL_COLOR_CLEAR_VALUE | Color-buffer clear value (RGBA mode) | color-buffer | 0, 0, 0, 0 | glGetFloatv()
GL_INDEX_CLEAR_VALUE | Color-buffer clear value (color-index mode) | color-buffer | 0 | glGetFloatv()
GL_DEPTH_CLEAR_VALUE | Depth-buffer clear value | depth-buffer | 1 | glGetIntegerv()
GL_STENCIL_CLEAR_VALUE | Stencil-buffer clear value | stencil-buffer | 0 | glGetIntegerv()
GL_ACCUM_CLEAR_VALUE | Accumulation-buffer clear value | accum-buffer | 0 | glGetFloatv()

Pixels

Table B-10 : (continued) Pixel State Variables

State Variable | Description | Attribute Group | Initial Value | Get Command
GL_UNPACK_SWAP_BYTES | Value of GL_UNPACK_SWAP_BYTES | pixel-store | GL_FALSE | glGetBooleanv()
GL_UNPACK_LSB_FIRST | Value of GL_UNPACK_LSB_FIRST | pixel-store | GL_FALSE | glGetBooleanv()
GL_UNPACK_ROW_LENGTH | Value of GL_UNPACK_ROW_LENGTH | pixel-store | 0 | glGetIntegerv()
GL_UNPACK_SKIP_ROWS | Value of GL_UNPACK_SKIP_ROWS | pixel-store | 0 | glGetIntegerv()
GL_UNPACK_SKIP_PIXELS | Value of GL_UNPACK_SKIP_PIXELS | pixel-store | 0 | glGetIntegerv()
GL_UNPACK_ALIGNMENT | Value of GL_UNPACK_ALIGNMENT | pixel-store | 4 | glGetIntegerv()
GL_PACK_SWAP_BYTES | Value of GL_PACK_SWAP_BYTES | pixel-store | GL_FALSE | glGetBooleanv()
GL_PACK_LSB_FIRST | Value of GL_PACK_LSB_FIRST | pixel-store | GL_FALSE | glGetBooleanv()
GL_PACK_ROW_LENGTH | Value of GL_PACK_ROW_LENGTH | pixel-store | 0 | glGetIntegerv()
GL_PACK_SKIP_ROWS | Value of GL_PACK_SKIP_ROWS | pixel-store | 0 | glGetIntegerv()
GL_PACK_SKIP_PIXELS | Value of GL_PACK_SKIP_PIXELS | pixel-store | 0 | glGetIntegerv()
GL_PACK_ALIGNMENT | Value of GL_PACK_ALIGNMENT | pixel-store | 4 | glGetIntegerv()
GL_MAP_COLOR | True if colors are mapped | pixel | GL_FALSE | glGetBooleanv()
GL_MAP_STENCIL | True if stencil values are mapped | pixel | GL_FALSE | glGetBooleanv()
GL_INDEX_SHIFT | Value of GL_INDEX_SHIFT | pixel | 0 | glGetIntegerv()
GL_INDEX_OFFSET | Value of GL_INDEX_OFFSET | pixel | 0 | glGetIntegerv()
GL_x_SCALE | Value of GL_x_SCALE; x is GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, or GL_DEPTH | pixel | 1 | glGetFloatv()
GL_x_BIAS | Value of GL_x_BIAS; x is one of GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, or GL_DEPTH | pixel | 0 | glGetFloatv()
GL_ZOOM_X | x zoom factor | pixel | 1.0 | glGetFloatv()
GL_ZOOM_Y | y zoom factor | pixel | 1.0 | glGetFloatv()
GL_x | glPixelMap() translation tables; x is a map name from Table 8-1 | - | 0's | glGetPixelMap*()
GL_x_SIZE | Size of table x | - | 1 | glGetIntegerv()
GL_READ_BUFFER | Read source buffer | pixel | - | glGetIntegerv()

Evaluators

Table B-11 : Evaluator State Variables

State Variable | Description | Attribute Group | Initial Value | Get Command
GL_ORDER | 1D map order | - | 1 | glGetMapiv()
GL_ORDER | 2D map orders | - | 1, 1 | glGetMapiv()
GL_COEFF | 1D control points | - | - | glGetMapfv()
GL_COEFF | 2D control points | - | - | glGetMapfv()
GL_DOMAIN | 1D domain endpoints | - | - | glGetMapfv()
GL_DOMAIN | 2D domain endpoints | - | - | glGetMapfv()
GL_MAP1_x | 1D map enables: x is map type | eval/enable | GL_FALSE | glIsEnabled()
GL_MAP2_x | 2D map enables: x is map type | eval/enable | GL_FALSE | glIsEnabled()
GL_MAP1_GRID_DOMAIN | 1D grid endpoints | eval | 0, 1 | glGetFloatv()
GL_MAP2_GRID_DOMAIN | 2D grid endpoints | eval | 0, 1; 0, 1 | glGetFloatv()
GL_MAP1_GRID_SEGMENTS | 1D grid divisions | eval | 1 | glGetFloatv()
GL_MAP2_GRID_SEGMENTS | 2D grid divisions | eval | 1,1 | glGetFloatv()
GL_AUTO_NORMAL | True if automatic normal generation enabled | eval | GL_FALSE | glIsEnabled()

Hints

Table B-12 : Hint State Variables

State Variable | Description | Attribute Group | Initial Value | Get Command
GL_PERSPECTIVE_CORRECTION_HINT | Perspective correction hint | hint | GL_DONT_CARE | glGetIntegerv()
GL_POINT_SMOOTH_HINT | Point smooth hint | hint | GL_DONT_CARE | glGetIntegerv()
GL_LINE_SMOOTH_HINT | Line smooth hint | hint | GL_DONT_CARE | glGetIntegerv()
GL_POLYGON_SMOOTH_HINT | Polygon smooth hint | hint | GL_DONT_CARE | glGetIntegerv()
GL_FOG_HINT | Fog hint | hint | GL_DONT_CARE | glGetIntegerv()

Implementation-Dependent Values

Table B-13 : (continued) Implementation-Dependent State Variables

State Variable | Description | Attribute Group | Minimum Value | Get Command
GL_MAX_LIGHTS | Maximum number of lights | - | 8 | glGetIntegerv()
GL_MAX_CLIP_PLANES | Maximum number of user clipping planes | - | 6 | glGetIntegerv()
GL_MAX_MODELVIEW_STACK_DEPTH | Maximum modelview-matrix stack depth | - | 32 | glGetIntegerv()
GL_MAX_PROJECTION_STACK_DEPTH | Maximum projection-matrix stack depth | - | 2 | glGetIntegerv()
GL_MAX_TEXTURE_STACK_DEPTH | Maximum depth of texture matrix stack | - | 2 | glGetIntegerv()
GL_SUBPIXEL_BITS | Number of bits of subpixel precision in x and y | - | 4 | glGetIntegerv()
GL_MAX_TEXTURE_SIZE | See discussion in "Texture Proxy" in Chapter 9 | - | 64 | glGetIntegerv()
GL_MAX_PIXEL_MAP_TABLE | Maximum size of a glPixelMap() translation table | - | 32 | glGetIntegerv()
GL_MAX_NAME_STACK_DEPTH | Maximum selection-name stack depth | - | 64 | glGetIntegerv()
GL_MAX_LIST_NESTING | Maximum display-list call nesting | - | 64 | glGetIntegerv()
GL_MAX_EVAL_ORDER | Maximum evaluator polynomial order | - | 8 | glGetIntegerv()
GL_MAX_VIEWPORT_DIMS | Maximum viewport dimensions | - | - | glGetIntegerv()
GL_MAX_ATTRIB_STACK_DEPTH | Maximum depth of the attribute stack | - | 16 | glGetIntegerv()
GL_MAX_CLIENT_ATTRIB_STACK_DEPTH | Maximum depth of the client attribute stack | - | 16 | glGetIntegerv()
GL_AUX_BUFFERS | Number of auxiliary buffers | - | 0 | glGetBooleanv()
GL_RGBA_MODE | True if color buffers store RGBA | - | - | glGetBooleanv()
GL_INDEX_MODE | True if color buffers store indices | - | - | glGetBooleanv()
GL_DOUBLEBUFFER | True if front and back buffers exist | - | - | glGetBooleanv()
GL_STEREO | True if left and right buffers exist | - | - | glGetBooleanv()
GL_POINT_SIZE_RANGE | Range (low to high) of antialiased point sizes | - | 1, 1 | glGetFloatv()
GL_POINT_SIZE_GRANULARITY | Antialiased point-size granularity | - | - | glGetFloatv()
GL_LINE_WIDTH_RANGE | Range (low to high) of antialiased line widths | - | 1, 1 | glGetFloatv()
GL_LINE_WIDTH_GRANULARITY | Antialiased line-width granularity | - | - | glGetFloatv()

Implementation-Dependent Pixel Depths

Table B-14 : Implementation-Dependent Pixel-Depth State Variables (continued)

State Variable | Description | Attribute Group | Minimum Value | Get Command
GL_RED_BITS | Number of bits per red component in color buffers | - | - | glGetIntegerv()
GL_GREEN_BITS | Number of bits per green component in color buffers | - | - | glGetIntegerv()
GL_BLUE_BITS | Number of bits per blue component in color buffers | - | - | glGetIntegerv()
GL_ALPHA_BITS | Number of bits per alpha component in color buffers | - | - | glGetIntegerv()
GL_INDEX_BITS | Number of bits per index in color buffers | - | - | glGetIntegerv()
GL_DEPTH_BITS | Number of depth-buffer bitplanes | - | - | glGetIntegerv()
GL_STENCIL_BITS | Number of stencil bitplanes | - | - | glGetIntegerv()
GL_ACCUM_RED_BITS | Number of bits per red component in the accumulation buffer | - | - | glGetIntegerv()
GL_ACCUM_GREEN_BITS | Number of bits per green component in the accumulation buffer | - | - | glGetIntegerv()
GL_ACCUM_BLUE_BITS | Number of bits per blue component in the accumulation buffer | - | - | glGetIntegerv()
GL_ACCUM_ALPHA_BITS | Number of bits per alpha component in the accumulation buffer | - | - | glGetIntegerv()

Miscellaneous

Table B-15 : Miscellaneous State Variables

State Variable | Description | Attribute Group | Initial Value | Get Command
GL_LIST_BASE | Setting of glListBase() | list | 0 | glGetIntegerv()
GL_LIST_INDEX | Number of display list under construction; 0 if none | - | 0 | glGetIntegerv()
GL_LIST_MODE | Mode of display list under construction; undefined if none | - | 0 | glGetIntegerv()
GL_ATTRIB_STACK_DEPTH | Attribute stack pointer | - | 0 | glGetIntegerv()
GL_CLIENT_ATTRIB_STACK_DEPTH | Client attribute stack pointer | - | 0 | glGetIntegerv()
GL_NAME_STACK_DEPTH | Name stack depth | - | 0 | glGetIntegerv()
GL_RENDER_MODE | glRenderMode() setting | - | GL_RENDER | glGetIntegerv()
GL_SELECTION_BUFFER_POINTER | Pointer to selection buffer | select | 0 | glGetPointerv()
GL_SELECTION_BUFFER_SIZE | Size of selection buffer | select | 0 | glGetIntegerv()
GL_FEEDBACK_BUFFER_POINTER | Pointer to feedback buffer | feedback | 0 | glGetPointerv()
GL_FEEDBACK_BUFFER_SIZE | Size of feedback buffer | feedback | 0 | glGetIntegerv()
GL_FEEDBACK_BUFFER_TYPE | Type of feedback buffer | feedback | GL_2D | glGetIntegerv()
- | Current error code(s) | - | 0 | glGetError()


Appendix C - OpenGL Programming Guide

OpenGL Programming Guide
Programming Guide > Appendix c

Appendix C
WGL: OpenGL Extension for Microsoft Windows NT and Windows 95

OpenGL rendering is supported on systems that run Microsoft Windows NT and Windows 95. The functions and routines of the Win32 library are necessary to initialize the pixel format and control rendering for OpenGL. Some routines, which are prefixed by wgl, extend Win32 so that OpenGL can be fully supported.

For Win32/WGL, the PIXELFORMATDESCRIPTOR is the key data structure to maintain pixel format information about the OpenGL window. A variable of data type PIXELFORMATDESCRIPTOR keeps track of pixel information, including pixel type (RGBA or color index), single- or double- buffering, resolution of colors, and presence of depth, stencil, and accumulation buffers.

To get more information about WGL, you may want to start with technical articles available through the Microsoft Developer Network at

http://www.microsoft.com/msdn/

Initialization

Use GetVersion() or the newer GetVersionEx() to determine version information. ChoosePixelFormat() tries to find a PIXELFORMATDESCRIPTOR with specified attributes. If a good match for the requested pixel format is found, then SetPixelFormat() should be called to actually use the pixel format. You should select a pixel format in the device context before calling wglCreateContext().

If you want to find out details about a given pixel format, use DescribePixelFormat() or, for overlays or underlays, wglDescribeLayerPlane().

Controlling Rendering

Several WGL routines are provided for creating and managing an OpenGL rendering context, rendering to a bitmap, swapping front and back buffers, finding a color palette, and using either bitmap or outline fonts.

Managing an OpenGL Rendering Context

wglCreateContext() creates an OpenGL rendering context for drawing on the device in the selected pixel format of the device context. (To create an OpenGL rendering context for overlay or underlay windows, use wglCreateLayerContext() instead.) To make a rendering context current, use wglMakeCurrent(); wglGetCurrentContext() returns the current context. You can also obtain the current device context with wglGetCurrentDC(). You can copy some OpenGL state variables from one context to another with wglCopyContext() or make two contexts share the same display lists and texture objects with wglShareLists(). When you're finished with a particular context, destroy it with wglDestroyContext().

OpenGL Rendering to a Bitmap

Win32 has a few routines to allocate (and deallocate) bitmaps, to which you can render OpenGL directly. CreateDIBitmap() creates a device-dependent bitmap (DDB) from a device-independent bitmap (DIB). CreateDIBSection() creates a device-independent bitmap (DIB) that applications can write to directly. When finished with your bitmap, you can use DeleteObject() to free it up.

Synchronizing Execution

If you want to combine GDI and OpenGL rendering, be aware there are no equivalents to functions like glXWaitGL(), glXWaitX(), or pglWaitGL() in Win32. Although glXWaitGL() has no equivalent in Win32, you can achieve the same effect by calling glFinish(), which waits until all pending OpenGL commands are executed, or by calling GdiFlush(), which waits until all GDI drawing has completed.

Swapping Buffers

For windows that are double-buffered, the front and back buffers can be exchanged by calling SwapBuffers() or wglSwapLayerBuffers(); the latter for overlays and underlays.

Finding a Color Palette

To access the color palette for the standard (non-layer) bitplanes, use the standard GDI functions to set the palette entries. For overlay or underlay layers, use wglRealizeLayerPalette(), which maps palette entries from a given color-index layer plane into the physical palette or initializes the palette of an RGBA layer plane. wglGetLayerPaletteEntries() is used to query the entries in palettes of layer planes.

Using a Bitmap or Outline Font

WGL has two routines, wglUseFontBitmaps() and wglUseFontOutlines(), for converting system fonts to use with OpenGL. Both routines build a display list for each requested character from the specified font and font size.

WGL Prototypes

Initialization

Determine version information:

BOOL GetVersion ( LPOSVERSIONINFO lpVersionInformation );

BOOL GetVersionEx ( LPOSVERSIONINFO lpVersionInformation );

Pixel format availability, selection, and capability:

int ChoosePixelFormat ( HDC hdc,
CONST PIXELFORMATDESCRIPTOR * ppfd );

BOOL SetPixelFormat ( HDC hdc, int iPixelFormat,
CONST PIXELFORMATDESCRIPTOR * ppfd );

int DescribePixelFormat ( HDC hdc, int iPixelFormat, UINT nBytes,
LPPIXELFORMATDESCRIPTOR ppfd );

BOOL wglDescribeLayerPlane ( HDC hdc, int iPixelFormat,
int iLayerPlane, UINT nBytes, LPLAYERPLANEDESCRIPTOR plpd );

Controlling Rendering

Manage or query an OpenGL rendering context:

HGLRC wglCreateContext ( HDC hdc );

HGLRC wglCreateLayerContext ( HDC hdc, int iLayerPlane );

BOOL wglShareLists ( HGLRC hglrc1, HGLRC hglrc2 );

BOOL wglDeleteContext ( HGLRC hglrc );

BOOL wglCopyContext ( HGLRC hglrcSource, HGLRC hlglrcDest,
UINT mask );

BOOL wglMakeCurrent ( HDC hdc, HGLRC hglrc );

HGLRC wglGetCurrentContext (VOID) ;

HDC wglGetCurrentDC (VOID);

Access and release the bitmap of the front buffer:

HBITMAP CreateDIBitmap ( HDC hdc,
CONST BITMAPINFOHEADER *lpbmih, DWORD fdwInit,
CONST VOID *lpbInit, CONST BITMAPINFO *lpbmi, UINT fuUsage );

HBITMAP CreateDIBSection ( HDC hdc, CONST BITMAPINFO *pbmi,
UINT iUsage, VOID *ppvBits, HANDLE hSection, DWORD dwOffset );

BOOL DeleteObject ( HGDIOBJ hObject );

Exchange front and back buffers:

BOOL SwapBuffers ( HDC hdc );

BOOL wglSwapLayerBuffers ( HDC hdc, UINT fuPlanes );

Finding a color palette for overlay or underlay layers:

int wglGetLayerPaletteEntries ( HDC hdc, int iLayerPlane, int iStart,
int cEntries, CONST COLORREF *pcr );

BOOL wglRealizeLayerPalette ( HDC hdc, int iLayerPlane,
BOOL bRealize );

Use a bitmap or an outline font:

BOOL wglUseFontBitmaps ( HDC hdc, DWORD first, DWORD count,
DWORD listBase );

BOOL wglUseFontOutlines ( HDC hdc, DWORD first, DWORD count,
DWORD listBase, FLOAT deviation, FLOAT extrusion, int format,
LPGLYPHMETRICSFLOAT lpgmf );


Appendix D - OpenGL Programming Guide

OpenGL Programming Guide
Programming Guide > Appendix d

Appendix D
Basics of GLUT: The OpenGL Utility Toolkit

This appendix describes a subset of Mark Kilgard's OpenGL Utility Toolkit (GLUT), which is fully documented in his book, OpenGL Programming for the X Window System (Reading, MA: Addison-Wesley Developers Press, 1996). GLUT has become a popular library for OpenGL programmers, because it standardizes and simplifies window and event management. GLUT has been ported atop a variety of OpenGL implementations, including both the X Window System and Microsoft Windows NT.

This appendix has the following major sections:

  • "Initializing and Creating a Window"
  • "Handling Window and Input Events"
  • "Loading the Color Map"
  • "Initializing and Drawing Three-Dimensional Objects"
  • "Managing a Background Process"
  • "Running the Program"

(See "How to Obtain the Sample Code" in the Preface for information about how to obtain the source code for GLUT.)

With GLUT, your application structures its event handling to use callback functions. (This method is similar to using the Xt Toolkit, also known as the X Intrinsics, with a widget set.) For example, first you open a window and register callback routines for specific events. Then, you create a main loop without an exit. In that loop, if an event occurs, its registered callback functions are executed. Upon completion of the callback functions, flow of control is returned to the main loop.

Initializing and Creating a Window

Before you can open a window, you must specify its characteristics: Should it be single-buffered or double-buffered? Should it store colors as RGBA values or as color indices? Where should it appear on your display? To specify the answers to these questions, call glutInit(), glutInitDisplayMode(), glutInitWindowSize(), and glutInitWindowPosition() before you call glutCreateWindow() to open the window.

void glutInit(int argc, char **argv);
glutInit() should be called before any other GLUT routine, because it initializes the GLUT library. glutInit() will also process command line options, but the specific options are window system dependent. For the X Window System, -iconic, -geometry, and -display are examples of command line options, processed by glutInit(). (The parameters to the glutInit() should be the same as those to main().)
void glutInitDisplayMode(unsigned int mode);
Specifies a display mode (such as RGBA or color-index, or single- or double-buffered) for windows created when glutCreateWindow() is called. You can also specify that the window have an associated depth, stencil, and/or accumulation buffer. The mask argument is a bitwise ORed combination of GLUT_RGBA or GLUT_INDEX, GLUT_SINGLE or GLUT_DOUBLE, and any of the buffer-enabling flags: GLUT_DEPTH, GLUT_STENCIL, or GLUT_ACCUM. For example, for a double-buffered, RGBA-mode window with a depth and stencil buffer, use GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL. The default value is GLUT_RGBA | GLUT_SINGLE (an RGBA, single-buffered window).
void glutInitWindowSize(int width, int height);
void glutInitWindowPosition(int x, int y);
Requests windows created by glutCreateWindow() to have an initial size and position. The arguments (x, y) indicate the location of a corner of the window, relative to the entire display. The width and height indicate the window's size (in pixels). The initial window size and position are hints and may be overridden by other requests.
int glutCreateWindow(char *name);
Opens a window with previously set characteristics (display mode, width, height, and so on). The string name may appear in the title bar if your window system does that sort of thing. The window is not initially displayed until glutMainLoop() is entered, so do not render into the window until then.
The value returned is a unique integer identifier for the window. This identifier can be used for controlling and rendering to multiple windows (each with an OpenGL rendering context) from the same application.

Handling Window and Input Events

After the window is created, but before you enter the main loop, you should register callback functions using the following routines.

void glutDisplayFunc(void (*func)(void));
Specifies the function that's called whenever the contents of the window need to be redrawn. The contents of the window may need to be redrawn when the window is initially opened, when the window is popped and window damage is exposed, and when glutPostRedisplay() is explicitly called.
void glutReshapeFunc(void (*func)(int width, int height));
Specifies the function that's called whenever the window is resized or moved. The argument func is a pointer to a function that expects two arguments, the new width and height of the window. Typically, func calls glViewport(), so that the display is clipped to the new size, and it redefines the projection matrix so that the aspect ratio of the projected image matches the viewport, avoiding aspect ratio distortion. If glutReshapeFunc() isn't called or is deregistered by passing NULL, a default reshape function is called, which calls glViewport(0, 0, width, height).
void glutKeyboardFunc(void (*func)(unsigned int key, int x, int y);
Specifies the function, func, that's called when a key that generates an ASCII character is pressed. The key callback parameter is the generated ASCII value. The x and y callback parameters indicate the location of the mouse (in window-relative coordinates) when the key was pressed.
void glutMouseFunc(void (*func)(int button, int state, int x, int y));
Specifies the function, func, that's called when a mouse button is pressed or released. The button callback parameter is one of GLUT_LEFT_BUTTON, GLUT_MIDDLE_BUTTON, or GLUT_RIGHT_BUTTON. The state callback parameter is either GLUT_UP or GLUT_DOWN, depending upon whether the mouse has been released or pressed. The x and y callback parameters indicate the location (in window-relative coordinates) of the mouse when the event occurred.
void glutMotionFunc(void (*func)(int x, int y));
Specifies the function, func, that's called when the mouse pointer moves within the window while one or more mouse buttons is pressed. The x and y callback parameters indicate the location (in window-relative coordinates) of the mouse when the event occurred.
void glutPostRedisplay(void);
Marks the current window as needing to be redrawn. At the next opportunity, the callback function registered by glutDisplayFunc() will be called.

Loading the Color Map

If you're using color-index mode, you might be surprised to discover there's no OpenGL routine to load a color into a color lookup table. This is because the process of loading a color map depends entirely on the window system. GLUT provides a generalized routine to load a single color index with an RGB value, glutSetColor().

void glutSetColor(GLint index, GLfloat red, GLfloat green, GLfloat blue);
Loads the index in the color map, index, with the given red, green, and blue values. These values are normalized to lie in the range [0.0,1.0].

Initializing and Drawing Three-Dimensional Objects

Many sample programs in this guide use three-dimensional models to illustrate various rendering properties. The following drawing routines are included in GLUT to avoid having to reproduce the code to draw these models in each program. The routines render all their graphics in immediate mode. Each three-dimensional model comes in two flavors: wireframe without surface normals, and solid with shading and surface normals. Use the solid version when you're applying lighting. Only the teapot generates texture coordinates.

void glutWireSphere(GLdouble radius, GLint slices, GLint stacks);
void glutSolidSphere(GLdouble radius, GLint slices, GLint stacks);
void glutWireCube(GLdouble size);
void glutSolidCube(GLdouble size);
void glutWireTorus(GLdouble innerRadius, GLdouble outerRadius,
GLint nsides, GLint rings);
void glutSolidTorus(GLdouble innerRadius, GLdouble outerRadius,
GLint nsides, GLint rings);
void glutWireIcosahedron(void);
void glutSolidIcosahedron(void);
void glutWireOctahedron(void);
void glutSolidOctahedron(void);
void glutWireTetrahedron(void);
void glutSolidTetrahedron(void);
void glutWireDodecahedron(GLdouble radius);
void glutSolidDodecahedron(GLdouble radius);
void glutWireCone(GLdouble radius, GLdouble height, GLint slices,
GLint stacks);
void glutSolidCone(GLdouble radius, GLdouble height, GLint slices,
GLint stacks);
void glutWireTeapot(GLdouble size);
void glutSolidTeapot(GLdouble size);

Managing a Background Process

You can specify a function that's to be executed if no other events are pending - for example, when the event loop would otherwise be idle - with glutIdleFunc(). This is particularly useful for continuous animation or other background processing.

void glutIdleFunc(void (*func)(void));
Specifies the function, func, to be executed if no other events are pending. If NULL (zero) is passed in, execution of func is disabled.

Running the Program

After all the setup is completed, GLUT programs enter an event processing loop, glutMainLoop().

void glutMainLoop(void);
Enters the GLUT processing loop, never to return. Registered callback functions will be called when the corresponding events instigate them.


Appendix E - OpenGL Programming Guide

OpenGL Programming Guide
Programming Guide > Appendix e

Appendix E
Calculating Normal Vectors

This appendix describes how to calculate normal vectors for surfaces. You need to define normals to use the OpenGL lighting facility, which is described in Chapter 5. "Normal Vectors" in Chapter 2 introduces normals and the OpenGL command for specifying them. This appendix goes through the details of calculating them. It has the following major sections:

  • "Finding Normals for Analytic Surfaces"
  • "Finding Normals from Polygonal Data"

Since normals are perpendicular to a surface, you can find the normal at a particular point on a surface by first finding the flat plane that just touches the surface at that point. The normal is the vector that's perpendicular to that plane. On a perfect sphere, for example, the normal at a point on the surface is in the same direction as the vector from the center of the sphere to that point. For other types of surfaces, there are other, better means for determining the normals, depending on how the surface is specified.

Recall that smooth curved surfaces are approximated by a large number of small flat polygons. If the vectors perpendicular to these polygons are used as the surface normals in such an approximation, the surface appears faceted, since the normal direction is discontinuous across the polygonal boundaries. In many cases, however, an exact mathematical description exists for the surface, and true surface normals can be calculated at every point. Using the true normals improves the rendering considerably, as shown in Figure E-1. Even if you don't have a mathematical description, you can do better than the faceted look shown in the figure. The two major sections in this appendix describe how to calculate normal vectors for these two cases:

  • "Finding Normals for Analytic Surfaces" explains what to do when you have a mathematical description of a surface.
  • "Finding Normals from Polygonal Data" covers the case when you have only the polygonal data to describe a surface.

Figure E-1 : Rendering with Polygonal Normals vs. True Normals

Finding Normals for Analytic Surfaces

Analytic surfaces are smooth, differentiable surfaces that are described by a mathematical equation (or set of equations). In many cases, the easiest surfaces to find normals for are analytic surfaces for which you have an explicit definition in the following form:

V(s,t) = [ X(s,t) Y(s,t) Z(s,t) ]

where s and t are constrained to be in some domain, and X, Y, and Z are differentiable functions of two variables. To calculate the normal, find

which are vectors tangent to the surface in the s and t directions. The cross product

is perpendicular to both and, hence, to the surface. The following shows how to calculate the cross product of two vectors. (Watch out for the degenerate cases where the cross product has zero length!)

You should probably normalize the resulting vector. To normalize a vector [x y z], calculate its length

and divide each component of the vector by the length.

As an example of these calculations, consider the analytic surface

V(s,t) = [ s2 t3 3-st ]

From this we have

So, for example, when s=1 and t=2, the corresponding point on the surface is (1, 8, 1), and the vector (-24, 2, 24) is perpendicular to the surface at that point. The length of this vector is 34, so the unit normal vector is (-24/34, 2/34, 24/34) = (-0.70588, 0.058823, 0.70588).

For analytic surfaces that are described implicitly, as F(x, y, z) = 0, the problem is harder. In some cases, you can solve for one of the variables, say z = G(x, y), and put it in the explicit form given previously:

Then continue as described earlier.

If you can't get the surface equation in an explicit form, you might be able to make use of the fact that the normal vector is given by the gradient

evaluated at a particular point (x, y, z). Calculating the gradient might be easy, but finding a point that lies on the surface can be difficult. As an example of an implicitly defined analytic function, consider the equation of a sphere of radius 1 centered at the origin:

x2 + y2 + z2 - 1 = 0 )

This means that

F (x, y, z) = x2 + y2 + z2 - 1

which can be solved for z to yield

Thus, normals can be calculated from the explicit form

as described previously.

If you could not solve for z, you could have used the gradient

as long as you could find a point on the surface. In this case, it's not so hard to find a point - for example, (2/3, 1/3, 2/3) lies on the surface. Using the gradient, the normal at this point is (4/3, 2/3, 4/3). The unit-length normal is (2/3, 1/3, 2/3), which is the same as the point on the surface, as expected.

Finding Normals from Polygonal Data

As mentioned previously, you often want to find normals for surfaces that are described with polygonal data such that the surfaces appear smooth rather than faceted. In most cases, the easiest way for you to do this (though it might not be the most efficient way) is to calculate the normal vectors for each of the polygonal facets and then to average the normals for neighboring facets. Use the averaged normal for the vertex that the neighboring facets have in common. Figure E-2 shows a surface and its polygonal approximation. (Of course, if the polygons represent the exact surface and aren't merely an approximation - if you're drawing a cube or a cut diamond, for example - don't do the averaging. Calculate the normal for each facet as described in the following paragraphs, and use that same normal for each vertex of the facet.)

Figure E-2 : Averaging Normal Vectors

To find the normal for a flat polygon, take any three vertices v1, v2, and v3 of the polygon that do not lie in a straight line. The cross product

[v1 - v2] ´ [v2 - v3]

is perpendicular to the polygon. (Typically, you want to normalize the resulting vector.) Then you need to average the normals for adjoining facets to avoid giving too much weight to one of them. For instance, in the example shown in Figure E-2, if n1, n2, n3, and n4 are the normals for the four polygons meeting at point P, calculate n1+n2+n3+n4 and then normalize it. (You can get a better average if you weight the normals by the size of the angles at the shared intersection.) The resulting vector can be used as the normal for point P.

Sometimes, you need to vary this method for particular situations. For instance, at the boundary of a surface (for example, point Q in Figure E-2), you might be able to choose a better normal based on your knowledge of what the surface should look like. Sometimes the best you can do is to average the polygon normals on the boundary as well. Similarly, some models have some smooth parts and some sharp corners (point R is on such an edge in Figure E-2). In this case, the normals on either side of the crease shouldn't be averaged. Instead, polygons on one side of the crease should be drawn with one normal, and polygons on the other side with another.


Appendix F - OpenGL Programming Guide

OpenGL Programming Guide
Programming Guide > Appendix f

Appendix F
Homogeneous Coordinates and Transformation Matrices

This appendix presents a brief discussion of homogeneous coordinates. It also lists the form of the transformation matrices used for rotation, scaling, translation, perspective projection, and orthographic projection. These topics are introduced and discussed in Chapter 3. For a more detailed discussion of these subjects, see almost any book on three-dimensional computer graphics - for example, Computer Graphics: Principles and Practice by Foley, van Dam, Feiner, and Hughes (Reading, MA: Addison-Wesley, 1990) - or a text on projective geometry - for example, The Real Projective Plane, by H. S. M. Coxeter, 2nd ed. (Cambridge: Cambridge University Press, 1961). In the discussion that follows, the term homogeneous coordinates always means three-dimensional homogeneous coordinates, although projective geometries exist for all dimensions.

This appendix has the following major sections:

  • "Homogeneous Coordinates"
  • "Transformation Matrices"

Homogeneous Coordinates

OpenGL commands usually deal with two- and three-dimensional vertices, but in fact all are treated internally as three-dimensional homogeneous vertices comprising four coordinates. Every column vector (x, y, z, w)T represents a homogeneous vertex if at least one of its elements is nonzero. If the real number a is nonzero, then (x, y, z, w)T and (ax, ay, az, aw)T represent the same homogeneous vertex. (This is just like fractions: x/y = (ax)/(ay).) A three-dimensional euclidean space point (x, y, z)T becomes the homogeneous vertex with coordinates (x, y, z, 1.0)T, and the two-dimensional euclidean point (x, y)T becomes (x, y, 0.0, 1.0)T.

As long as w is nonzero, the homogeneous vertex (x, y, z, w)T corresponds to the three-dimensional point (x/w, y/w, z/w)T. If w = 0.0, it corresponds to no euclidean point, but rather to some idealized "point at infinity." To understand this point at infinity, consider the point (1, 2, 0, 0), and note that the sequence of points (1, 2, 0, 1), (1, 2, 0, 0.01), and (1, 2.0, 0.0, 0.0001), corresponds to the euclidean points (1, 2), (100, 200), and (10000, 20000). This sequence represents points rapidly moving toward infinity along the line 2x = y. Thus, you can think of (1, 2, 0, 0) as the point at infinity in the direction of that line.

Note: OpenGL might not handle homogeneous clip coordinates with w < 0 correctly. To be sure that your code is portable to all OpenGL systems, use only nonnegative w values.

Transforming Vertices

Vertex transformations (such as rotations, translations, scaling, and shearing) and projections (such as perspective and orthographic) can all be represented by applying an appropriate 4 ´ 4 matrix to the coordinates representing the vertex. If v represents a homogeneous vertex and M is a 4 ´ 4 transformation matrix, then Mv is the image of v under the transformation by M. (In computer-graphics applications, the transformations used are usually nonsingular - in other words, the matrix M can be inverted. This isn't required, but some problems arise with nonsingular transformations.)

After transformation, all transformed vertices are clipped so that x, y, and z are in the range [-w, w] (assuming w > 0). Note that this range corresponds in euclidean space to [-1.0, 1.0].

Transforming Normals

Normal vectors aren't transformed in the same way as vertices or position vectors. Mathematically, it's better to think of normal vectors not as vectors, but as planes perpendicular to those vectors. Then, the transformation rules for normal vectors are described by the transformation rules for perpendicular planes.

A homogeneous plane is denoted by the row vector (a, b, c, d), where at least one of a, b, c, or d is nonzero. If q is a nonzero real number, then (a, b, c, d) and (qa, qb, qc, qd) represent the same plane. A point (x, y, z, w)T is on the plane (a, b, c, d) if ax+by+cz+dw = 0. (If w = 1, this is the standard description of a euclidean plane.) In order for (a, b, c, d) to represent a euclidean plane, at least one of a, b, or c must be nonzero. If they're all zero, then (0, 0, 0, d) represents the "plane at infinity," which contains all the "points at infinity."

If p is a homogeneous plane and v is a homogeneous vertex, then the statement "v lies on plane p" is written mathematically as pv = 0, where pv is normal matrix multiplication. If M is a nonsingular vertex transformation (that is, a 4 ´ 4 matrix that has an inverse M-1), then pv = 0 is equivalent to pM-1Mv = 0, so Mv lies on the plane pM-1. Thus, pM-1 is the image of the plane under the vertex transformation M.

If you like to think of normal vectors as vectors instead of as the planes perpendicular to them, let v and n be vectors such that v is perpendicular to n. Then, nTv = 0. Thus, for an arbitrary nonsingular transformation M, nTM-1Mv = 0, which means that nTM-1 is the transpose of the transformed normal vector. Thus, the transformed normal vector is (M-1)Tn. In other words, normal vectors are transformed by the inverse transpose of the transformation that transforms points. Whew!

Transformation Matrices

Although any nonsingular matrix M represents a valid projective transformation, a few special matrices are particularly useful. These matrices are listed in the following subsections.

Translation

The call glTranslate*(x, y, z) generates T, where

Scaling

The call glScale*(x, y, z) generates S, where

Notice that S-1 is defined only if x, y, and z are all nonzero.

Rotation

The call glRotate*(a, x, y, z) generates R as follows:

Let v = (x, y, z)T, and u = v/||v|| = (x', y', z')T.

Also let

Then

The R matrix is always defined. If x=y=z=0, then R is the identity matrix. You can obtain the inverse of R, R-1, by substituting - &agr; for a, or by transposition.

The glRotate*() command generates a matrix for rotation about an arbitrary axis. Often, you're rotating about one of the coordinate axes; the corresponding matrices are as follows:

As before, the inverses are obtained by transposition.

Perspective Projection

The call glFrustum(l, r, b, t, n, f) generates R, where

R is defined as long as l ´ r, t ´ b, and n ´ f.

Orthographic Projection

The call glOrtho(l, r, b, t, n, f ) generates R, where

R is defined as long as l ´ r, t ´ b, and n ´ f.


Appendix G - OpenGL Programming Guide

OpenGL Programming Guide
Programming Guide > Appendix g

Appendix G
Programming Tips

This appendix lists some tips and guidelines that you might find useful. Keep in mind that these tips are based on the intentions of the designers of the OpenGL, not on any experience with actual applications and implementations! This appendix has the following major sections:

  • "OpenGL Correctness Tips"
  • "OpenGL Performance Tips"
  • "GLX Tips"

OpenGL Correctness Tips

  • Perform error checking often. Call glGetError() at least once each time the scene is rendered to make certain error conditions are noticed.
  • Do not count on the error behavior of an OpenGL implementation - it might change in a future release of OpenGL. For example, OpenGL 1.1 ignores matrix operations invoked between glBegin() and glEnd() commands, but a future version might not. Put another way, OpenGL error semantics may change between upward-compatible revisions.
  • If you need to collapse all geometry to a single plane, use the projection matrix. If the modelview matrix is used, OpenGL features that operate in eye coordinates (such as lighting and application-defined clipping planes) might fail.
  • Do not make extensive changes to a single matrix. For example, do not animate a rotation by continually calling glRotate*() with an incremental angle. Rather, use glLoadIdentity() to initialize the given matrix for each frame, then call glRotate*() with the desired complete angle for that frame.
  • Count on multiple passes through a rendering database to generate the same pixel fragments only if this behavior is guaranteed by the invariance rules established for a compliant OpenGL implementation. (See Appendix H for details on the invariance rules.) Otherwise, a different set of fragments might be generated.
  • Do not expect errors to be reported while a display list is being defined. The commands within a display list generate errors only when the list is executed.
  • Place the near frustum plane as far from the viewpoint as possible to optimize the operation of the depth buffer.
  • Call glFlush() to force all previous OpenGL commands to be executed. Do not count on glGet*() or glIs*() to flush the rendering stream. Query commands flush as much of the stream as is required to return valid data but don't guarantee completing all pending rendering commands.
  • Turn dithering off when rendering predithered images (for example, when glCopyPixels() is called).
  • Make use of the full range of the accumulation buffer. For example, if accumulating four images, scale each by one-quarter as it's accumulated.
  • If exact two-dimensional rasterization is desired, you must carefully specify both the orthographic projection and the vertices of primitives that are to be rasterized. The orthographic projection should be specified with integer coordinates, as shown in the following example:
gluOrtho2D(0, width, 0, height);

where width and height are the dimensions of the viewport. Given this projection matrix, polygon vertices and pixel image positions should be placed at integer coordinates to rasterize predictably. For example, glRecti(0, 0, 1, 1) reliably fills the lower left pixel of the viewport, and glRasterPos2i(0, 0) reliably positions an unzoomed image at the lower left of the viewport. Point vertices, line vertices, and bitmap positions should be placed at half-integer locations, however. For example, a line drawn from (x1, 0.5) to (x2, 0.5) will be reliably rendered along the bottom row of pixels into the viewport, and a point drawn at (0.5, 0.5) will reliably fill the same pixel as glRecti(0, 0, 1, 1).

An optimum compromise that allows all primitives to be specified at integer positions, while still ensuring predictable rasterization, is to translate x and y by 0.375, as shown in the following code fragment. Such a translation keeps polygon and pixel image edges safely away from the centers of pixels, while moving line vertices close enough to the pixel centers.

glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, width, 0, height);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(0.375, 0.375, 0.0);
/* render all primitives at integer positions */
  • Avoid using negative w vertex coordinates and negative q texture coordinates. OpenGL might not clip such coordinates correctly and might make interpolation errors when shading primitives defined by such coordinates.
  • Do not assume the precision of operations, based upon the data type of parameters to OpenGL commands. For example, if you are using glRotated(), you should not assume that geometric processing pipeline operates with double-precision floating point. It is possible that the parameters to glRotated() are converted to a different data type before processing.

OpenGL Performance Tips

  • Use glColorMaterial() when only a single material property is being varied rapidly (at each vertex, for example). Use glMaterial() for infrequent changes, or when more than a single material property is being varied rapidly.
  • Use glLoadIdentity() to initialize a matrix, rather than loading your own copy of the identity matrix.
  • Use specific matrix calls such as glRotate*(), glTranslate*(), and glScale*() rather than composing your own rotation, translation, or scale matrices and calling glMultMatrix().
  • Use query functions when your application requires just a few state values for its own computations. If your application requires several state values from the same attribute group, use glPushAttrib() and glPopAttrib() to save and restore them.
  • Use display lists to encapsulate potentially expensive state changes.
  • Use display lists to encapsulate the rendering calls of rigid objects that will be drawn repeatedly.
  • Use texture objects to encapsulate texture data. Place all the glTexImage*() calls (including mipmaps) required to completely specify a texture and the associated glTexParameter*() calls (which set texture properties) into a texture object. Bind this texture object to select the texture.
  • If the situation allows it, use gl*TexSubImage() to replace all or part of an existing texture image rather than the more costly operations of deleting and creating an entire new image.
  • If your OpenGL implementation supports a high-performance working set of resident textures, try to make all your textures resident; that is, make them fit into the high-performance texture memory. If necessary, reduce the size or internal format resolution of your textures until they all fit into memory. If such a reduction creates intolerably fuzzy textured objects, you may give some textures lower priority, which will, when push comes to shove, leave them out of the working set.
  • Use evaluators even for simple surface tessellations to minimize network bandwidth in client-server environments.
  • Provide unit-length normals if it's possible to do so, and avoid the overhead of GL_NORMALIZE. Avoid using glScale*() when doing lighting because it almost always requires that GL_NORMALIZE be enabled.
  • Set glShadeModel() to GL_FLAT if smooth shading isn't required.
  • Use a single glClear() call per frame if possible. Do not use glClear() to clear small subregions of the buffers; use it only for complete or near-complete clears.
  • Use a single call to glBegin(GL_TRIANGLES) to draw multiple independent triangles rather than calling glBegin(GL_TRIANGLES) multiple times, or calling glBegin(GL_POLYGON). Even if only a single triangle is to be drawn, use GL_TRIANGLES rather than GL_POLYGON. Use a single call to glBegin(GL_QUADS) in the same manner rather than calling glBegin(GL_POLYGON) repeatedly. Likewise, use a single call to glBegin(GL_LINES) to draw multiple independent line segments rather than calling glBegin(GL_LINES) multiple times.
  • Some OpenGL implementations benefit from storing vertex data in vertex arrays. Use of vertex arrays reduces function call overhead. Some implementations can improve performance by batch processing or reusing processed vertices.
  • In general, use the vector forms of commands to pass precomputed data, and use the scalar forms of commands to pass values that are computed near call time.
  • Avoid making redundant mode changes, such as setting the color to the same value between each vertex of a flat-shaded polygon.
  • Be sure to disable expensive rasterization and per-fragment operations when drawing or copying images. OpenGL will even apply textures to pixel images if asked to!
  • Unless absolutely needed, avoid having different front and back polygon modes.

GLX Tips

  • Use glXWaitGL() rather than glFinish() to force X rendering commands to follow GL rendering commands.
  • Likewise, use glXWaitX() rather than XSync() to force GL rendering commands to follow X rendering commands.
  • Be careful when using glXChooseVisual(), because boolean selections are matched exactly. Since some implementations won't export visuals with all combinations of boolean capabilities, you should call glXChooseVisual() several times with different boolean values before you give up. For example, if no single-buffered visual with the required characteristics is available, check for a double-buffered visual with the same capabilities. It might be available, and it's easy to use.


Appendix H - OpenGL Programming Guide

OpenGL Programming Guide
Programming Guide > Appendix h

Appendix H
OpenGL Invariance

OpenGL is not a pixel-exact specification. It therefore doesn't guarantee an exact match between images produced by different OpenGL implementations. However, OpenGL does specify exact matches, in some cases, for images produced by the same implementation. This appendix describes the invariance rules that define these cases.

The obvious and most fundamental case is repeatability. A conforming OpenGL implementation generates the same results each time a specific sequence of commands is issued from the same initial conditions. Although such repeatability is useful for testing and verification, it's often not useful to application programmers, because it's difficult to arrange for equivalent initial conditions. For example, rendering a scene twice, the second time after swapping the front and back buffers, doesn't meet this requirement. So repeatability can't be used to guarantee a stable, double-buffered image.

A simple and useful algorithm that counts on invariant execution is erasing a line by redrawing it in the background color. This algorithm works only if rasterizing the line results in the same fragment x,y pairs being generated in both the foreground and background color cases. OpenGL requires that the coordinates of the fragments generated by rasterization be invariant with respect to framebuffer contents, which color buffers are enabled for drawing, the values of matrices other than those on the top of the matrix stacks, the scissor parameters, all writemasks, all clear values, the current color, index, normal, texture coordinates, and edge-flag values, the current raster color, raster index, and raster texture coordinates, and the material properties. It is further required that exactly the same fragments be generated, including the fragment color values, when framebuffer contents, color buffer enables, matrices other than those on the top of the matrix stacks, the scissor parameters, writemasks, or clear values differ.

OpenGL further suggests, but doesn't require, that fragment generation be invariant with respect to the matrix mode, the depths of the matrix stacks, the alpha test parameters (other than alpha test enable), the stencil parameters (other than stencil enable), the depth test parameters (other than depth test enable), the blending parameters (other than enable), the logical operation (but not logical operation enable), and the pixel-storage and pixel-transfer parameters. Because invariance with respect to several enables isn't recommended, you should use other parameters to disable functions when invariant rendering is required. For example, to render invariantly with blending enabled and disabled, set the blending parameters to GL_ONE and GL_ZERO to disable blending rather than calling glDisable(GL_BLEND). Alpha testing, stencil testing, depth testing, and the logical operation all can be disabled in this manner.

Finally, OpenGL requires that per-fragment arithmetic, such as blending and the depth test, is invariant to all OpenGL state except the state that directly defines it. For example, the only OpenGL parameters that affect how the arithmetic of blending is performed are the source and destination blend parameters and the blend enable parameter. Blending is invariant to all other state changes. This invariance holds for the scissor test, the alpha test, the stencil test, the depth test, blending, dithering, logical operations, and buffer writemasking.

As a result of all these invariance requirements, OpenGL can guarantee that images rendered into different color buffers, either simultaneously or separately using the same command sequence, are pixel identical. This holds for all the color buffers in the framebuffer or all the color buffers in an off-screen buffer, but it isn't guaranteed between the framebuffer and off-screen buffers.


Appendix J - OpenGL Programming Guide

OpenGL Programming Guide
Programming Guide > Appendix i

Appendix I
Color Plates

This appendix contains the color plates that appear in the printed version of this guide.

Plate 1. The scene from the cover of this book, with the objects rendered as wireframe models. See Chapter 2.

Figure J-1 : Plate 1

Plate 2. The same scene using fog for depth-cueing (lines further from the eye are dimmer). See Chapter 7.

Figure J-2 : Plate 2

Plate 3. The same scene with antialiased lines that smooth the jagged edges. See Chapter 7.

Figure J-3 : Plate 3

Plate 4. The scene drawn with flat-shaded polygons (a single color for each filled polygon). See Chapter 5.

Figure J-4 : Plate 4

Plate 5. The scene rendered with lighting and smooth-shaded polygons. See Chapter 5 and Chapter 6.

Figure J-5 : Plate 5

Plate 6. The scene with texture maps and shadows added. See Chapter 9 and Chapter 14.

Figure J-6 : Plate 6

Plate 7. The scene drawn with one of the objects motion-blurred. The accumulation buffer is used to compose the sequence of images needed to blur the moving object. See Chapter 10.

Figure J-7 : Plate 7

Plate 8. A close-up shot - the scene is rendered from a new viewpoint. See Chapter 3.

Figure J-8 : Plate 8

Plate 9. The scene drawn using atmospheric effects (fog) to simulate a smoke-filled room. See Chapter 7.

Figure J-9 : Plate 9

Plate 10. Teapots drawn with jittered viewing volumes into the accumulation buffer for a depth-of-field effect. The gold teapot is in sharpest focus. See Chapter 10.

Figure J-10 : Plate 10

Plate 11. A smooth-shaded triangle. The three vertices at the corners are drawn in red, green, and blue; the rest of the triangle is smoothly shaded between these three colors. See Chapter 5.

Figure J-11 : Plate 11

Plate 12. The color cube. (a) The red, green, and blue axes are shown; (b) The axes denote yellow, cyan, and magenta. See Chapter 5

Figure J-12 : Plate 12a

Figure J-13 : Plate 12b

Plate 13. Objects drawn with gray material parameters and colored light sources. (a) This scene has pale blue ambient light and a white diffuse light source. (b) This scene has a pale blue diffuse light source and almost no ambient light. (c) An infinite light source is used; (d) A local light source is used. With the infinite light source, the highlight (specular reflection) is centered on both the cone and the sphere because the angle between the object and the line of sight is ignored. With a local light source, the angle is taken into account, so the highlights are located appropriately on both objects. See Chapter 6.

Figure J-14 : Plate 13a

Figure J-15 : Plate 13b

Figure J-16 : Plate 13c

Figure J-17 : Plate 13d

Plate 14. Gray teapots drawn with different lighting conditions. (a) Each of the three teapots is drawn with increasing ambient light. (b) The teapots are clipped to expose their interiors. The top teapot uses one-sided lighting, the middle one uses two-sided lighting with the same material for both front and back faces, and the bottom teapot uses two-sided lighting and different materials for the front and back faces. See Chapter 6.

Figure J-18 : Plate 14a

Figure J-19 : Plate 14b

Plate 15. A lighted sphere drawn using color index mode. See Chapter 6.

Figure J-20 : Plate 15

Plate 16. Twelve spheres, each with different material parameters. The row properties are as follows: row 1 - No ambient reflection; row 2 - Grey ambient reflection; row 3 - Blue ambient reflection. The first column uses a blue diffuse material color with no specular properties. The second column adds white specular reflection with a low shininess exponent. The third column uses a high shininess exponent and thus has a more concentrated highlight. The fourth column uses the blue diffuse color and, instead of specular reflection, adds an emissive component. See Chapter 6.

Figure J-21 : Plate 16

Plate 17. Lighted, smooth-shaded teapots drawn with different material properties that approximate real materials. The first column has materials that resemble (from top to bottom) emerald, jade, obsidian, pearl, ruby, and turquoise. The second column resembles brass, bronze, chrome, copper, gold, and silver. The third column represents various colors of plastic: black, cyan, green, red, white, and yellow. The fourth column is drawn with similar colors of rubber. See Chapter 6.

Figure J-22 : Plate 17

Plate 18. Lighted, green teapots drawn using automatic texture-coordinate generation and a red contour texture map. (a) The texture contour stripes are parallel to the plane x = 0, relative to the transformed object (that is, using GL_OBJECT_LINEAR). As the object moves, the texture appears to be attached to it. (b) A different planar equation (x + y + z = 0) is used, so the stripes have a different orientation. (c) The texture coordinates are calculated relative to eye coordinates and hence aren't fixed to the object (GL_EYE_LINEAR). As the object moves, it appears to "swim" through the texture. See Chapter 9.

Figure J-23 : Plate 18a

Figure J-24 : Plate 18b

Figure J-25 : Plate 18c

Plate 19. A texture-mapped Bezier surface mesh created using evaluators. See Chapter 9 and Chapter 12.

Figure J-26 : Plate 19

Plate 20. A single polygon drawn using a set of mipmapped textures. In this case, each texture is simply a different color. The polygon is actually a rectangle oriented so that it recedes into the distance, appearing to become progressively smaller. As the visible area of the polygon becomes smaller, correspondingly smaller mipmaps are used. See Chapter 9.

Figure J-27 : Plate 20

Plate 21. An environment-mapped object. In (a) is the original texture, a processed photograph of a coffee shop in Palo Alto, taken with a very wide-angle lens. (b) Is a goblet with the environment map applied; because of the mapping, the goblet appears to reflect the coffee shop off its surface. See Chapter 9.

Figure J-28 : Plate 21a

Figure J-29 : Plate 21b

Plate 22. A scene with several flat-shaded objects. In (a), the scene is aliased. In (b), the accumulation buffer is used for scene antialiasing: the scene is rendered several times, each time jittered less than one pixel, and the images are accumulated and then averaged.See Chapter 10.

Figure J-30 : Plate 22a

Figure J-31 : Plate 22b

Plate 23. A magnification of the previous scenes. (a) The image shows the aliased, jagged edges. (b) The edges are blurred, or antialiased, and hence less jagged. See Chapter 10.

Figure J-32 : Plate 23a

Figure J-33 : Plate 23b

Plate 24. A scene drawn with texture mapping, lighting, and shadows. The paintings, floor, ceiling, and benches are texture-mapped. Note the use of spotlights and shadows. See Chapter 6 , Chapter 9, and Chapter 14.

Figure J-34 : Plate 24

Plate 25. A lighted, smooth-shaded model on a texture-mapped surface. See Chapter 5 , Chapter 6, and Chapter 9.

Figure J-35 : Plate 25

Plate 26. A dramatically lit and shadowed scene, with most of the surfaces textured. The iris is a polygonal model. See Chapter 2 , Chapter 6, Chapter 9, and Chapter 14.

Figure J-36 : Plate 26

Plate 27. Sophisticated use of texturing. All surfaces are texture-mapped. In addition, the attenuated spotlight effect is created using a projected texture. See Chapter 9 and Chapter 14.

Figure J-37 : Plate 27

Plate 28. Lit, smooth-shaded three-dimensional font. The font is created by extruding a two-dimensional shape along a specified axis. See Chapter 2 , Chapter 5, and Chapter 6.

Figure J-38 : Plate 28

Plates 29 and 30. Two scenes snapped from a visual simulation program. The hills are composed of just a few polygons, but all the polygons are texture-mapped. Similarly, the buildings are composed of only a few textured rectangular walls. See Chapter 2 , Chapter 3, and Chapter 9.

Figure J-39 : Plate 29

Figure J-40 : Plate 30

Plate 31. Another scene from a different visual simulation program. The hills are textured, and the scene is rendered with fog. The airplanes, obviously, are polygonal. See Chapter 2 , Chapter 3, Chapter 7, and Chapter 9.

Figure J-41 : Plate 31


Tutoriels sur la programmation OpenGL moderne (>=3)

Vu dans ... https://opengl.developpez.com/tutoriels/opengl-tutorial/

Bienvenue dans le sommaire des tutoriels OpenGL moderne (OpenGL 3.3 et supérieur).

Tutoriels

  • Ouvrir une fenêtre
  • Le premier triangle
  • Les matrices
  • Un cube coloré
  • Un cube texturé
  • Clavier et souris
  • Chargement d'un modèle
  • Shaders de base
  • Indexation VBO
  • La transparence
  • Texte 2D
  • Extensions OpenGL
  • Application des normales
  • Rendu dans une texture
  • Textures de lumière (lightmap)
  • Textures d'ombre (shadow mapping)
  • Les rotations (quaternions)
  • Billboards
  • Particules/instanciation

Divers

  • Outils et liens utiles
  • FAQ
  • Compteur de FPS
  • Compiler vos propres applications C
  • Cliquer sur un objet avec un hack OpenGL
  • Cliquer sur un objet avec une bibliothèque physique
  • Cliquer sur un objet avec une fonction personnalisée

Code source

Vous pouvez télécharger le code source des tutoriels :
Pour OpenGL 3.3 (la version normale) ;
Pour OpenGL 2.1 (le portage OpenGL 2.1 (pour des raisons de compatibilité)).

Vous pouvez aussi retrouver le code source sur Google Code.

La licence utilisée pour ce code est "DO WHAT THE FUCK YOU WANT TO PUBLIC LICENCE", soit, elle vous permet de faire tout ce que vous voulez.


OpenGL Moderne - tutoriel 1 : ouvrir une fenêtre

Vu dans ... https://opengl.developpez.com/tutoriels/opengl-tutorial/1-ouvrir-une-fenetre/

ouvrir une fenêtre

I. Introduction

Bienvenue dans le premier tutoriel !

Avant de s'attaquer à OpenGL, vous allez d'abord apprendre à compiler le code accompagnant chaque tutoriel, apprendre à l'exécuter, et plus important, apprendre avec le code.

II. Prérequis

Aucun prérequis n'est nécessaire pour suivre ces tutoriels. Une expérience avec n'importe quel langage de programmation (C, Java, Lisp, JavaScript, ou autre) est préférable pour comprendre le code, mais pas nécessaire ; il sera simplement plus difficile d'apprendre deux choses à la fois.

Tous les tutoriels sont écrits en « C++ simple » : beaucoup d'efforts ont été faits pour rendre le code aussi simple que possible. Pas de templates, pas de classes, pas de pointeurs. De cette façon, vous allez pouvoir tout comprendre même si vous ne connaissez que Java.

III. Oubliez tout

Vous n'avez rien besoin de connaître, mais vous devez oublier tout ce que vous connaissez sur OpenGL. Si vous connaissez quelque chose qui ressemble à glBegin(), oubliez-le. Ici vous allez apprendre l'OpenGL moderne (OpenGL 3 et 4), et la plupart des tutoriels en ligne apprennent le « vieux » OpenGL (OpenGL 1 et 2). Donc oubliez tout ce que vous pourriez savoir, avant que votre cerveau ne fonde face à ce mélange.

IV. Compiler les tutoriels

Tous les tutoriels peuvent être compilés sur Windows, Linux et Mac. Pour ces plates-formes, la procédure est grossièrement la même :

  1. mettez à jour vos pilotes !! Faites-le. Vous avez été prévenus ;
  2. télécharger un compilateur, si vous n'en avez pas encore ;
  3. installez CMake ;
  4. téléchargez le code source des tutoriels ;
  5. générez un projet avec CMake ;
  6. compilez le projet ;
  7. jouez avec les exemples.

IV-A. Compilation sous Windows

  1. La mise à jour de vos pilotes devrait être simple. Allez sur le site de NVIDIA ou AMD et téléchargez les pilotes. Si vous n'êtes pas sûr de connaître votre carte graphique : Panneau de configuration → Système et Sécurité → Système → Gestionnaire de périphériques → Cartes graphiques. Si vous avez un GPU intégré Intel, les pilotes sont généralement fournis par votre OEM (Dell, HP…).
  2. On suggère d'utiliser Visual Studio 2010 Express comme compilateur. Vous pouvez le télécharger gratuitement ici. Si vous préférez utiliser MinGW, on recommande d'utiliser Qt Creator. Installez celui que vous voulez. Les étapes suivantes seront expliquées pour Visual Studio mais devraient être similaires pour tout autre EDI.
  3. Téléchargez CMake et installez-le.
  4. Téléchargez le code source et décompressez-le, par exemple dans C:\Users\XYZ\Projects\OpenGLTutorials\.
  5. Démarrez CMake. Dans la première ligne, naviguez jusqu'au dossier décompressé. Si vous n'êtes pas sûr, choisissez le dossier contenant le fichier CMakeLists.txt. Dans la seconde ligne, entrez le chemin où vous souhaitez que les fichiers spécifiques au compilateur figurent. Par exemple, vous pouvez choisir C:\Users\XYZ\Projects\OpenGLTutorials-build-Visual2010-32bits\, ou C:\Users\XYZ\Projects\OpenGLTutorials\build\Visual2010-32bits\. Notez que cela peut être n'importe où et pas nécessairement dans le même dossier. CMake
  6. Cliquez sur le bouton « Configurer » (« Configure »). Comme c'est la première fois que vous configurez le projet, CMake vous demandera quel compilateur vous désirez utiliser. Choisissez-le suivant la première étape. Si vous avez un Windows 64 bits, vous pouvez choisir 64 bits ; si vous ne savez pas, choisissez 32 bits.
  7. Cliquez sur « Configurer » (« Configure ») jusqu'à ce que les lignes rouges disparaissent. Cliquez sur « Générer » (« Generate »). Votre projet Visual Studio est maintenant créé. Vous pouvez oublier CMake ; désinstallez-le si vous le souhaitez.
  8. Ouvrez C:\Users\XYZ\Projects\OpenGLTutorials-build-Visual2010-32bits\. Vous allez voir un fichier Tutorials.sln : ouvrez-le avec Visual Studio. Répertoire du projet
    Dans le menu « Générer » (« Build »), cliquez sur « Tout générer » (« Build All »). Tous les tutoriels et les dépendances vont être compilés. Chaque exécutable sera aussi copié dans C:\Users\XYZ\Projects\OpenGLTutorials\. Avec un peu de chance, aucune erreur n'apparaît. Visual Studio 2010 et les tutoriels
  9. Ouvrez C:\Users\XYZ\Projects\OpenGLTutorials\playground et démarrez playground.exe. Une fenêtre noire devrait apparaître. Fenêtre vide

Vous pouvez aussi démarrer n'importe quel tutoriel à partir de Visual Studio. Faites un clic droit sur Playground et choisissez « Définir comme projet de démarrage » (« Choose as startup project »). Vous pouvez maintenant déboguer le code en appuyant sur F5.

Répertoire de travail des tutoriels
Projet de démarrage de Visual Studio

IV-B. Compilation sous Linux

Il y a tellement de variantes disponibles de Linux qu'il est impossible de lister toutes les plates-formes possibles. Adaptez les instructions si possible et n'hésitez pas à lire la documentation de votre distribution.

  1. Installez les derniers pilotes. On recommande chaudement les pilotes binaires propriétaires. Ce n'est pas GNU ou autre, mais au moins ils fonctionnent. Si votre distribution ne fournit pas d'installation automatique, essayez le guide d'Ubuntu.
  2. Installez tous les compilateurs, outils et bibliothèques nécessaires. La liste complète est : cmake make g++ libx11-dev libgl1-mesa-dev libglu1-mesa-dev libxrandr-dev libxext-dev. Utilisez sudo apt-get install ***** ou su && yum install ******.
  3. Téléchargez le code source et décompressez-le, par exemple dans ~/Projects/OpenGLTutorials/.

  4. cd dans ~/Projects/OpenGLTutorials/ et entrez les commandes suivantes :

    1. mkdir build
      cd build
      cmake ..
  5. Un makefile a été créé dans le répertoire build/.
  6. Tapez « make all ». Chaque tutoriel et dépendance sera compilé. Chaque exécutable sera aussi copié dans ~/Projects/OpenGLTutorials/. Avec un peu de chance, aucune erreur n'apparaît.
  7. Ouvrez ~/Projects/OpenGLTutorials/playground et lancez ./playground. Une fenêtre noire devrait apparaître.

Notez que vous devriez vraiment utiliser un IDE comme Qt Creator. En particulier, celui-ci supporte les projets CMake nativement et fournira une meilleure expérience au débogage. Voici les instructions pour Qt Creator :

  1. Dans Qt Creator, allez dans « Fichier » (« File ») → « Outils » (« Tools ») → « Options » → « Compilation et exécution » (« Compile&Execute) → CMake ;
  2. Définissez le chemin vers CMake. Celui-ci est probablement /usr/bin/cmake ;
  3. « Fichier » (« File ») → « Ouvrir projet » (« Open Project ») ; Sélectionnez tutorials/CMakeLists.txt ;
  4. Sélectionnez un répertoire de compilation, si possible en dehors du répertoire des tutoriels ;
  5. Définissez optionnellement -DCMAKE_BUILD_TYPE=Debug dans la boite des paramètres. Validez ;
  6. Cliquez sur le marteau en bas. Les tutoriels peuvent maintenant être lancés à partir du répertoire tutorials/ ;
  7. Pour exécuter les tutoriels dans Qt Creator, cliquez sur « Projets » (« Projects ») → « Paramètres d'exécution » (« Execution parameters ») → « Répertoire de travail » (« Working Directory ») et sélectionnez le répertoire où les shaders, textures et modèles sont. Par exemple, pour le second tutoriel : ~/opengl-tutorial/tutorial02_red_triangle/.

IV-C. Compilation sur Mac

Mac OS ne supporte pas OpenGL 3.3. Les récents Mac avec MacOS 10.7 Lion et les GPU compatibles peuvent exécuter OpenGL 3.2, mais pas 3.3. Utilisez le portage OpenGL 2.1 des tutoriels à la place. À part ce point, la procédure est très similaire à celle de Windows (les Makefiles sont aussi supportés, mais on ne l'expliquera pas ici) :

  1. installez XCode à partir du Mac App Store ;
  2. téléchargez CMake et installez le fichier .dmg. Vous n'avez pas besoin d'installer les outils en ligne de commande ;
  3. téléchargez le code source (version 2.1!!) et décompressez-le, par exemple dans ~/Projects/OpenGLTutorials/.
  4. Lancez CMake (Applications → CMake). Dans la première ligne, naviguez jusqu'au dossier décompressé. Si vous n'êtes pas sûr, choisissez le dossier contenant le fichier CMakeLists.txt. Dans la seconde ligne, entrez l'emplacement où vous souhaitez mettre tous les fichiers spécifiques au compilateur. Par exemple, vous pouvez choisir ~/Projects/OpenGLTutorials_bin_XCode/. Notez que cela peut être n'importe où et pas nécessairement dans le même dossier.
  5. Cliquez sur le bouton « Configurer » (« Configure »). Comme c'est la première fois que vous configurez le projet, CMake vous demandera quel est le compilateur à utiliser. Choisissez XCode.
  6. Cliquez sur « Configurer » (« Configure ») jusqu'à ce que les lignes rouges disparaissent. Cliquez sur « Générer » (« Generate »). Votre projet XCode est maintenant créé. Vous pouvez oublier CMake ; désinstallez-le si vous le souhaitez.
  7. Ouvrez ~/Projects/OpenGLTutorials_bin_XCode/. Vous allez voir un fichier Tutorials.xcodeproj : Ouvrez-le.
  8. Sélectionnez le tutoriel dans le panneau de projet que vous souhaitez lancer et utilisez le bouton « Lancer » (« Run ») pour compiler et exécuter : Lancement projet sous XCode

IV-D. Notes pour Code::Blocks

À cause de deux bogues (un dans Code::Blocks, un dans CMake), vous devez éditer la ligne de commande dans Projet (« Project ») → « Options de compilation » (« Build Options ») → « Commandes Make » (« Make commands »), comme suit :

Configuration Code::Blocks et CMake

Vous devez aussi configurer le répertoire de travail vous-même : « Projet » (« Project ») → « Propriétés » (« Properties ») → « Cibles de compilation » (« Build targets ») → tutorial N → « Répertoire d'exécution » (« execution working dir ») (c'est src_dir/tutorial_N/).

V. Exécuter les tutoriels

Vous devez exécuter les tutoriels directement à partir du bon répertoire : simplement en double cliquant sur l'exécutable. Si vous préférez la ligne de commande, cd vers le bon répertoire.

Si vous souhaitez exécuter les tutoriels à partir de l'IDE, n'oubliez pas de lire les instructions ci-dessus pour définir le bon répertoire de travail.

VI. Comment suivre ces tutoriels

Chaque tutoriel est fourni avec son code source et ses données, qui se trouvent dans tutorialXX/. Par contre, vous ne devez jamais modifier ces projets : ils ne servent que de référence. Ouvrez playground/playground.cpp et modifiez ce fichier à la place. Torturez-le comme vous le souhaitez. Si vous êtes perdu, copiez n'importe quel tutoriel dans celui-ci et tout devrait revenir à la normale.

On va fournir des morceaux de code tout au long des tutoriels. N'hésitez pas à les copier/coller dans votre bac à sable durant la lecture : l'expérimentation est une bonne chose. Évitez de vous limiter à lire le code fini : vous n'allez pas apprendre grand-chose comme cela. Même avec un simple copier/coller, vous allez avoir votre lot de problèmes.

VII. Ouvrir une fenêtre

Finalement ! Du code OpenGL !

Enfin, pas vraiment. Tous les tutoriels vous montrent la façon « bas niveau » de faire les choses afin que vous puissiez voir qu'il n'y a pas de magie. Mais cette partie est actuellement très ennuyeuse et inutile, donc on va utiliser GLFW, une bibliothèque externe qui fera cela à notre place. Si vous le souhaitez vraiment, vous pouvez utiliser la Win32 API sous Windows, la X11 API sous Linux et Cocoa sur Mac ; ou utilisez une autre bibliothèque comme SFML, FreeGLUT, SDL, … regardez la page des liens.

Ok, allons-y. Premièrement, on doit gérer les dépendances : on a besoin de choses basiques pour afficher les messages dans la console :

Sélectionnez
// Inclure les entêtes standard
#include <stdio.h>
#include <stdlib.h>

Premièrement, GLEW. Celle-ci est actuellement un peu magique, mais laissez-la pour plus tard.

Sélectionnez
// Inclure GLEW. Toujours l'inclure avant gl.h et glfw.h, car c'est un peu magique.
#include <GL/glew.h>

On a décidé de laisser GLFW gérer la fenêtre et le clavier, donc incluez-la aussi :

Sélectionnez
// Inclure GLFW
#include <GL/glfw3.h>

Pour l'instant, on n'a pas besoin de celle-ci, mais c'est la bibliothèque pour les opérations mathématiques liées à la 3D. Elle va se montrer rapidement très utile. Il n'y a pas de magie dans GLM, vous pouvez écrire votre propre bibliothèque si vous le souhaitez, c'est pratique. Le « using namespace » permet d'éviter de taper « glm::vec3 », au lieu de « vec3 ».

Sélectionnez
// Inclure GLM
#include <glm/glm.hpp>
using namespace glm;

Si vous copiez/collez toutes ces inclusions dans playground.cpp, le compilateur se plaindra, car il n'y a pas de fonction main(). Donc, créons-la :

Sélectionnez
int main() {

La première chose à faire est d'initialiser GLFW :

Sélectionnez
// Initialise GLFW
if( !glfwInit() )
{
    fprintf( stderr, "Failed to initialize GLFW\n" );
    return -1;
}

On peut maintenant créer notre premier fenêtre OpenGL !

Sélectionnez
glfwOpenWindowHint(GLFW_FSAA_SAMPLES, 4); // antialiasing 4x
glfwOpenWindowHint(GLFW_OPENGL_VERSION_MAJOR, 3); // Nous voulons OpenGL 3.3
glfwOpenWindowHint(GLFW_OPENGL_VERSION_MINOR, 3);
glfwOpenWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // Nous ne voulons pas de support de l'ancien OpenGL

// Ouvre une fenêtre et crée son contexte OpenGL
GLFWwindow* window; // (Dans le code joint, cette variable est globale)
window = glfwCreateWindow( 1024, 768, "Tutorial 01", NULL, NULL);
if( window == NULL ){
    fprintf( stderr, "Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials.\n" );
    glfwTerminate();
    return -1;
}
glfwMakeContextCurrent(window);

// Initialise GLEW
glewExperimental=true; // Nécessaire pour le profil core
if (glewInit() != GLEW_OK) {
    fprintf(stderr, "Failed to initialize GLEW\n");
    return -1;
}

Compilez et exécutez ce code. Une fenêtre devrait apparaître et se fermer immédiatement. Évidemment ! On doit attendre que l'utilisateur appuie sur la touche Échap :

Sélectionnez
// S'assure que l'on puisse capturer la touche échap utilisée plus bas
glfwSetInputMode(window, GLFW_STICKY_KEYS, GL_TRUE);

do{
    // N'affiche rien, à bientôt dans le deuxième tutoriel !

    // Échange les tampons
    glfwSwapBuffers();

} // Vérifie si la touche échap a été appuyée ou si la fenêtre a été fermée
while( glfwGetKey(window, GLFW_KEY_ESCAPE ) != GLFW_PRESS &&
glfwWindowShouldClose(window) == 0 );

Et cela conclut notre premier tutoriel ! Dans le deuxième tutoriel, vous allez apprendre comment afficher un triangle.


OpenGL Moderne - tutoriel 2 : le premier triangle

Vu dans ... https://opengl.developpez.com/tutoriels/opengl-tutorial/2-premier-triangle/

le premier triangle

I. Introduction

Cela va être un autre long tutoriel.

OpenGL 3 facilite l'écriture des choses compliquées, mais possède l'inconvénient de rendre l'affichage d'un simple triangle relativement difficile.

N'oubliez pas de copier/coller le code régulièrement.

Si le programme crashe au démarrage, vous l'exécutez certainement à partir du mauvais répertoire. Lisez PRÉCAUTIONNEUSEMENT le premier tutoriel sur comment configurer Visual Studio.

II. Les Vertex Array Object

Je ne vais pas m'enfoncer dans les détails maintenant, mais vous devez créer un Vertex Array Object (VAO) et le définir comment objet courant.

Sélectionnez
GLuint VertexArrayID;
glGenVertexArrays(1, &VertexArrayID);
glBindVertexArray(VertexArrayID);

Faites-le une fois que votre fenêtre est créée (= après la création du contexte OpenGL) et avant tout autre appel OpenGL.

Si vous souhaitez vraiment en apprendre plus sur les VAO, il y a quelques autres tutoriels sur le Web, mais ce n'est pas très important.

III. Coordonnées écran

Un triangle est défini par trois points. Lorsque l'on parle de « points » en graphismes 3D, on utilise habituellement le terme de « sommet » (en anglais « vertex », « vertices » au pluriel). Un sommet possède trois coordonnées : X, Y et Z. Vous pouvez imaginer ces trois coordonnées de la manière suivante :

  • X est sur votre droite ;
  • Y, vers le haut ;
  • Z est derrière vous (oui, derrière et non devant).

Mais voici une meilleure méthode pour les visualiser : utilisez la règle de la main droite :

  • X est votre pouce ;
  • Y, votre index ;
  • Z est votre majeur. Si vous placez votre pouce sur la droite et votre index vers le ciel, votre majeur pointera aussi derrière votre dos.

Il est étrange d'avoir l'axe Z dans cette direction. Pourquoi est-ce ainsi ? Pour faire court : car cent années de « règle de la main droite » vous donneront des outils pratiques. La seule contrepartie est un axe Z contre-intuitif.

Mis à part cela, remarquez aussi que vous pouvez bouger votre main librement : votre X, Y et Z suivront. On reviendra sur ce point.

Donc, on a besoin de trois points 3D afin de faire un triangle ; les voici :

Sélectionnez
// Un tableau de trois vecteurs qui représentent trois sommets
static const GLfloat g_vertex_buffer_data[] = {
   -1.0f, -1.0f, 0.0f,
   1.0f, -1.0f, 0.0f,
   0.0f,  1.0f, 0.0f,
};

Le premier sommet est (-1, -1, 0). Cela signifie que, sauf si nous le transformons d'une quelconque manière, il sera affiché à la position (-1, -1) à l'écran. Qu'est-ce que cela donne ? L'origine de l'écran est au centre, l'axe X va vers la droite, comme toujours et l'axe Y vers le haut. Voici ce que cela donne sur un écran large :

Coordonnées sur un écran

C'est une chose que vous ne pouvez modifier, c'est intégré dans votre carte graphique. Donc (-1, -1) est le coin inférieur gauche de votre écran. (1, -1) est le coin inférieur droit et (0, 1), le milieu haut. Donc ce triangle devrait couvrir la majorité de l'écran.

IV. Dessiner notre triangle

La prochaine étape est de fournir ce triangle à OpenGL. Pour cela il faut créer un tampon(1) :

Sélectionnez
// Ceci identifiera notre tampon de sommets
GLuint vertexbuffer;

// Génère un tampon et place l'identifiant dans 'vertexbuffer'
glGenBuffers(1, &vertexbuffer);

// Les commandes suivantes vont parler de notre tampon 'vertexbuffer'
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);

// Fournit les sommets à OpenGL.
glBufferData(GL_ARRAY_BUFFER, sizeof(g_vertex_buffer_data), g_vertex_buffer_data, GL_STATIC_DRAW);

Cela n'est nécessaire qu'une seule fois au lancement du programme.

Maintenant, la boucle principale, où on a l'habitude de ne « rien » dessiner. On peut maintenant dessiner un fantastique triangle :

Sélectionnez
// premier tampon d'attributs : les sommets
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
glVertexAttribPointer(
   0,                  // attribut 0. Aucune raison particulière pour 0, mais cela doit correspondre au « layout » dans le shader
   3,                  // taille
   GL_FLOAT,           // type
   GL_FALSE,           // normalisé ?
   0,                  // nombre d'octets séparant deux sommets dans le tampon
   (void*)0            // décalage du tableau de tampon
);

// Dessine le triangle !
glDrawArrays(GL_TRIANGLES, 0, 3); // Démarre à partir du sommet 0; 3 sommets au total -> 1 triangle

glDisableVertexAttribArray(0);

Si vous avec une carte NVIDIA, vous pouvez voir le résultat (pour les autres cartes, continuez de lire) :

Triangle sans shader

Ce blanc est bien ennuyeux. Voyons voir comment l'améliorer en l'affichant en rouge. Cela peut être fait avec quelque chose appelé « shader ».

V. Shaders

V-A. Compilation de shader

Dans la configuration la plus simple, vous avez besoin de deux shaders : un appelé « Vertex Shader », qui sera exécuté pour chaque sommet et l'autre appelé « Fragment Shader », qui sera exécuté pour chaque fragment. Comme on utilise un antialiasing 4x, on a quatre échantillons pour chaque pixel.

Les shaders se programment avec un langage appelé GLSL : GL Shader Language, qui est intégré à OpenGL. Contrairement au C ou au Java, le GLSL doit être compilé durant l'exécution du programme, ce qui signifie que chaque fois que vous lancez votre application, tous vos shaders sont recompilés.

Les deux shaders sont généralement dans des fichiers distincts. Dans cet exemple, nous avons SimpleFragmentShader.fragmentshader et SimpleVertexShader.vertexshader. L'extension importe peu, cela aurait pu être .txt ou .glsl.

Voici enfin le code pour charger des shaders. Il n'est pas très important de le comprendre entièrement, car vous ne le faites qu'une seule fois dans le programme, les commentaires suffiront. Comme cette fonction va être utilisée dans tous les autres tutoriels, elle est placée dans un fichier à part : common/loadShader.cpp. Notez que tout comme les tampons, les shaders ne sont pas directement accessibles : nous n'avons qu'un identifiant. L'implémentation actuelle est cachée par le pilote.

Sélectionnez
GLuint LoadShaders(const char * vertex_file_path,const char * fragment_file_path){

    // Crée les shaders
    GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
    GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);

    // Lit le code du vertex shader à partir du fichier
    std::string VertexShaderCode;
    std::ifstream VertexShaderStream(vertex_file_path, std::ios::in);
    if(VertexShaderStream.is_open())
    {
        std::string Line = "";
        while(getline(VertexShaderStream, Line))
            VertexShaderCode += "\n" + Line;
        VertexShaderStream.close();
    }

    // Lit le code du fragment shader à partir du fichier
    std::string FragmentShaderCode;
    std::ifstream FragmentShaderStream(fragment_file_path, std::ios::in);
    if(FragmentShaderStream.is_open()){
        std::string Line = "";
        while(getline(FragmentShaderStream, Line))
            FragmentShaderCode += "\n" + Line;
        FragmentShaderStream.close();
    }

    GLint Result = GL_FALSE;
    int InfoLogLength;

    // Compile le vertex shader
    printf("Compiling shader : %s\n", vertex_file_path);
    char const * VertexSourcePointer = VertexShaderCode.c_str();
    glShaderSource(VertexShaderID, 1, &VertexSourcePointer , NULL);
    glCompileShader(VertexShaderID);

    // Vérifie le vertex shader
    glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result);
    glGetShaderiv(VertexShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
    std::vector<char> VertexShaderErrorMessage(InfoLogLength);
    glGetShaderInfoLog(VertexShaderID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]);
    fprintf(stdout, "%s\n", &VertexShaderErrorMessage[0]);

    // Compile le fragment shader
    printf("Compiling shader : %s\n", fragment_file_path);
    char const * FragmentSourcePointer = FragmentShaderCode.c_str();
    glShaderSource(FragmentShaderID, 1, &FragmentSourcePointer , NULL);
    glCompileShader(FragmentShaderID);

    // Vérifie le fragment shader
    glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result);
    glGetShaderiv(FragmentShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
    std::vector<char> FragmentShaderErrorMessage(InfoLogLength);
    glGetShaderInfoLog(FragmentShaderID, InfoLogLength, NULL, &FragmentShaderErrorMessage[0]);
    fprintf(stdout, "%s\n", &FragmentShaderErrorMessage[0]);

    // Lit le programme
    fprintf(stdout, "Linking program\n");
    GLuint ProgramID = glCreateProgram();
    glAttachShader(ProgramID, VertexShaderID);
    glAttachShader(ProgramID, FragmentShaderID);
    glLinkProgram(ProgramID);

    // Vérifie le programme
    glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
    glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength);
    std::vector<char> ProgramErrorMessage( max(InfoLogLength, int(1)) );
    glGetProgramInfoLog(ProgramID, InfoLogLength, NULL, &ProgramErrorMessage[0]);
    fprintf(stdout, "%s\n", &ProgramErrorMessage[0]);

    glDeleteShader(VertexShaderID);
    glDeleteShader(FragmentShaderID);

    return ProgramID;
}

V-B. Notre vertex shader

Écrivons le vertex shader.

La première ligne indique au compilateur que l'on va utiliser la syntaxe de OpenGL 3.

Sélectionnez
#version 330 core

La seconde ligne déclare les données d'entrées :

Sélectionnez
layout(location = 0) in vec3 vertexPosition_modelspace;

Expliquons-la en détail :

  • « vec3 » est un vecteur de trois composantes dans le GLSL. Il est similaire (mais différent) au glm::vec3 que nous avons utilisé pour définir notre triangle. Le point important est que si on utilise trois composantes en C++, nous utilisons aussi trois composantes dans le GLSL ;
  • « layout(location = 0) » se réfère au tampon que l'on fournit à l'attribut vertexPosition_modelspace. Chaque sommet peut avoir de nombreux attributs : une position, une ou plusieurs couleurs, une ou plusieurs coordonnées de texture et plein d'autres choses. OpenGL ne sait pas ce qu'est une couleur : il ne voit qu'un vec3. Donc on doit lui dire quel tampon correspond à quelle entrée. Nous le faisons en définissant le « layout » à la même valeur que le premier paramètre de la fonction glVertexAttribPointer. La valeur « 0 » n'est pas importante, cela aurait pu être 12 (mais, elle ne peut être supérieure à glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &v)), la chose importante est qu'elle soit la même des deux côtés ;
  • « vertexPosition_modelspace » aurait pu être n'importe quoi. Il contiendra la position des sommets pour chaque exécution du vertex shader ;
  • « in » signifie que ce sont des données d'entrée. Bientôt on va voir le mot clé « out ».

La fonction qui est appelée pour chaque sommet est appelée main, tout comme en C :

Sélectionnez
void main(){

La fonction principale va simplement définir la position du vertex à ce qui est dans le tampon. Donc, si on donne (1, 1), le triangle aura l'un de ces sommets au coin supérieur droit de l'écran. On verra dans le prochain tutoriel comment effectuer des calculs plus intéressants sur les positions passées au shader.

Sélectionnez
    gl_Position.xyz = vertexPosition_modelspace;
    gl_Position.w = 1.0;
 }

gl_Position est l'une des variables du langage : vous devez assigner une valeur à celle-ci. Tout le reste est optionnel ; on verra « tout le reste » dans le quatrième tutoriel.

V-C. Notre fragment shader

Pour le premier fragment shader, on va faire quelque chose de très simple : définir la couleur de chaque fragment à rouge. (Rappelez-vous, il y a quatre fragments dans un pixel, car nous utilisons l'antialiasing 4x.)

Sélectionnez
#version 330 core
out vec3 color;

void main(){
    color = vec3(1,0,0);
}

Donc voilà, vec3(1,0,0) signifie rouge. Cela est dû aux écrans d'ordinateur. La couleur est représentée par un triplet rouge, vert, bleu, dans cet ordre. Donc (1, 0, 0) indique complètement rouge, pas de vert et pas de bleu.

VI. Rassembler le tout

Avant la boucle principale, on appelle la fonction LoadShaders :

Sélectionnez
// Crée et compile notre programme GLSL à partir des shaders
GLuint programID = LoadShaders( "SimpleVertexShader.vertexshader", "SimpleFragmentShader.fragmentshader" );

En premier, dans la boucle principale, on nettoie l'écran. Cela changera la couleur de fond en bleu foncé à cause de l'appel glClearColor(0.0f, 0.0f, 0.4f, 0.0f) au-dessus de la boucle principale.

Sélectionnez
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

Puis on indique à OpenGL que l'on souhaite les shaders vus précédemment :

Sélectionnez
// Utilise notre shader
glUseProgram(programID);

// Dessine le triangle...

… et voilà, un triangle rouge !

Un triangle rouge en OpenGL

Dans le prochain tutoriel, on étudiera les transformations : comment définir une caméra, déplacer les objets, etc.


OpenGL Moderne - tutoriel 3 : matrices

w OpenGL Moderne - tutoriel 3 : matrices

Vu dans ... https://opengl.developpez.com/tutoriels/opengl-tutorial/3-matrices/

matrices

II-A. Une introduction aux matrices

De façon simple, une matrice est un tableau de nombres avec un nombre prédéfini de lignes et colonnes. Par exemple, une matrice 2 x 3 ressemble à ceci :

[ 2 9 5 8 7 1 ] [ 2 5 7 9 8 1 ]

Dans les graphismes 3D, nous utilisons uniquement des matrices 4 x 4. Elles vont permettre de transformer les vecteurs (x, y, z, w). Cela s'effectue en multipliant le sommet par la matrice :

Matrix x Sommet (dans cet ordre !!) = SommetTransformé

⎡ ⎣ ⎢ ⎢ ⎢ a e i m b f j n c g k o d h l p ⎤ ⎦ ⎥ ⎥ ⎥ × ⎡ ⎣ ⎢ ⎢ ⎢ x y z w ⎤ ⎦ ⎥ ⎥ ⎥ = ⎡ ⎣ ⎢ ⎢ ⎢ a x + b y + c z + d w e x + f y + g z + h w i x + j y + k z + l w m x + n y + o z + p w ⎤ ⎦ ⎥ ⎥ ⎥ [ x y z w ] = [ /div>

Ce n'est pas aussi effrayant qu'il y paraît. Placez votre doigt de la main gauche sur le 'a' et votre doigt de la main droite sur le 'x'. C'est 'ax'. Déplacez votre main gauche sur le prochain nombre (b) et votre main droite sur le prochain nombre (y). Vous obtenez 'by'. Encore une fois : 'cz'. Une nouvelle fois : 'dw'. ax + by + cz + dw. Vous avez votre nouveau 'x'. Faites de même pour chaque ligne et vous obtiendrez votre nouveau vecteur (x, y, z, w).

C'est très ennuyeux à calculer et on va le faire souvent, donc, votre ordinateur va le faire pour vous.

En C++, avec GLM :

Sélectionnez
glm::mat4 myMatrix;
glm::vec4 myVector;
// remplir myMatrix et myVector d'une façon ou d'une autre
glm::vec4 transformedVector = myMatrix * myVector; // Encore, dans cet ordre ! C'est important.

En GLSL :

Sélectionnez
mat4 myMatrix;
vec4 myVector;
// remplir myMatrix et myVector d'une façon ou d'une autre
vec4 transformedVector = myMatrix * myVector; // Super, c'est presque pareil qu'avec GLM

(avez-vous copié/collé ceci dans votre code ? Allez-y, essayez.)

II-B. Matrices de translation

Ce sont les plus simples matrices de transformation à comprendre. Une matrice de translation ressemble à ceci :

⎡ ⎣ ⎢ ⎢ ⎢ 1 0 0 0 0 1 0 0 0 0 1 0 X Y Z 1 ⎤ ⎦ ⎥ ⎥ ⎥ [

où X, Y, Z sont les valeurs que vous voulez ajouter à votre position.

Donc, si l'on veut déplacer le vecteur (10, 10, 10, 1) de dix unités sur l'axe des X, nous obtenons :

\begin{bmatrix} 1 & 0 & 0 & 10 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \\ \end{bmatrix} \times \begin{bmatrix} 10 \\ 10 \\ 10 \\ 1 \end{bmatrix} = \begin{bmatrix} 1 * 10 + 0 * 10 + 0 * 10 + 10 * 1 \\ 0 * 10 + 1 * 10 + 0 * 10 + 0 * 1 \\ 0 * 10 + 0 * 10 + 1 * 10 + 0 * 1 \\ 0 * 10 + 0 * 10 + 0 * 10 + 1 * 1 \end{bmatrix} = \begin{bmatrix} 10 + 0 + 0 + 10 \\ 0 + 10 + 0 + 0 \\ 0 + 0 + 10 + 0 \\ 0 + 0 + 0 + 1 \end{bmatrix} = \begin{bmatrix} 20 \\ 10 \\ 10 \\ 1 \end{bmatrix}

(Faites-le ! Faiiiites-le!)

… et on obtient un vecteur homogène (20, 10, 10, 1) ! Rappelez-vous, le 1 signifie que c'est une position et non pas une direction. Donc notre transformation n'a pas modifié le fait que nous utilisons une position, ce qui est une bonne chose.

Voyons voir maintenant ce qui se passe pour un vecteur qui représente une direction vers l'axe -Z : (0, 0, -1, 0)

\begin{bmatrix} 1 & 0 & 0 & 10 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \\ \end{bmatrix} \times \begin{bmatrix} 0 \\ 0 \\ -1 \\ 0 \end{bmatrix} = \begin{bmatrix} 1 * 0 + 0 * 0 + 0 * -1 + 10 * 0 \\ 0 * 0 + 1 * 0 + 0 * -1 + 0 * 0 \\ 0 * 0 + 0 * 0 + 1 * -1 + 0 * 0 \\ 0 * 0 + 0 * 0 + 0 * -1 + 1 * 0 \end{bmatrix} = \begin{bmatrix} 0 + 0 + 0 + 0 \\ 0 + 0 + 0 + 0 \\ 0 + 0 -1 + 0 \\ 0 + 0 + 0 + 0 \end{bmatrix} = \begin{bmatrix} 0 \\ 0 \\ -1 \\ 0 \end{bmatrix}

… soit, notre direction originale (0, 0, -1, 0), ce qui est juste, car comme je l'ai dit précédemment, le déplacement d'une direction n'a aucun sens.

Comment traduire cela en code ?

En C++, avec GLM :

Sélectionnez
#include <glm/transform.hpp> // après <glm/glm.hpp>

glm::mat4 myMatrix = glm::translate(10.0f, 0.0f, 0.0f);
glm::vec4 myVector(10.0f, 10.0f, 10.0f, 0.0f);
glm::vec4 transformedVector = myMatrix * myVector; // devinez le résultat

En GLSL : Bon, en fait, vous ne le ferez presque jamais. La plupart du temps, vous utilisez glm::translate() en C++ pour calculer votre matrice et vous l'envoyez au GLSL et vous la multipliez simplement par votre vecteur :

Sélectionnez
vec4 transformedVector = myMatrix * myVector;

II-C. La matrice d'identité

Celle-ci est spéciale. Elle ne fait rien. Mais je la mentionne car c'est aussi important que de savoir que A fois 1.0 donne A.

⎡ ⎣ ⎢ ⎢ ⎢ 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 ⎤ ⎦ ⎥ ⎥ ⎥ × ⎡ ⎣ ⎢ ⎢ ⎢ x y z w ⎤ ⎦ ⎥ ⎥ ⎥ = ⎡ ⎣ ⎢ ⎢ ⎢ 1 ∗ x + 0 ∗ y + 0 ∗ z + 0 ∗ w 0 ∗ x + 1 ∗ y + 0 ∗ z + 0 ∗ w 0 ∗ x + 0 ∗ y + 1 ∗ z + 0 ∗ w 0 ∗ x + 0 ∗ y + 0 ∗ z + 1 ∗ w ⎤ ⎦ ⎥ ⎥ ⎥ = ⎡ ⎣ ⎢ ⎢ ⎢ x + 0 + 0 + 0 0 + y + 0 + 0 0 + 0 + z + 0 0 + 0 + 0 + w ⎤ ⎦ ⎥ ⎥ ⎥ = ⎡ ⎣ ⎢ ⎢ ⎢ x y z w ⎤ ⎦ ⎥ ⎥ ⎥ [ 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 ] × [ x y z w ] = [ 1 ∗ x + 0 ∗ y + 0 ∗ z + 0 ∗ w 0 ∗ x + 1 ∗ y + 0 ∗ z + 0 ∗ w 0 ∗ x + 0 ∗ y + 1 ∗ z + 0 ∗ w 0 ∗ x + 0 ∗ y + 0 ∗ z + 1 ∗ w ] = [

En C++ :

Sélectionnez
glm::mat4 myIdentityMatrix = glm::mat4(1.0f);

II-D. Matrices de mise à l'échelle

Les matrices de mise à l'échelle sont aussi assez simples :

⎡ ⎣ ⎢ ⎢ ⎢ x 0 0 0 0 y 0 0 0 0 z 0 0 0 0 1 ⎤ ⎦ ⎥ ⎥ ⎥ [

Si vous souhaitez redimensionner un vecteur (une position ou une direction, peu importe) par 2.0 dans toutes les directions :

⎡ ⎣ ⎢ ⎢ ⎢ 2 0 0 0 0 2 0 0 0 0 2 0 0 0 0 1 ⎤ ⎦ ⎥ ⎥ ⎥ × ⎡ ⎣ ⎢ ⎢ ⎢ x y z w ⎤ ⎦ ⎥ ⎥ ⎥ = ⎡ ⎣ ⎢ ⎢ ⎢ 2 ∗ x + 0 ∗ y + 0 ∗ z + 0 ∗ w 0 ∗ x + 2 ∗ y + 0 ∗ z + 0 ∗ w 0 ∗ x + 0 ∗ y + 2 ∗ z + 0 ∗ w 0 ∗ x + 0 ∗ y + 0 ∗ z + 1 ∗ w ⎤ ⎦ ⎥ ⎥ ⎥ = ⎡ ⎣ ⎢ ⎢ ⎢ 2 ∗ x + 0 + 0 + 0 0 + 2 ∗ y + 0 + 0 0 + 0 + 2 ∗ z + 0 0 + 0 + 0 + 1 ∗ w ⎤ ⎦ ⎥ ⎥ ⎥ = ⎡ ⎣ ⎢ ⎢ ⎢ 2 ∗ x 2 ∗ y 2 ∗ z w ⎤ ⎦ ⎥ ⎥ ⎥ [ 2 0 0 0 0 2 0 0 0 0 2 0 0 0 0 1 ] × [ x y z w ] = [ 2 ∗ x + 0 ∗ y + 0 ∗ z + 0 ∗ w 0 ∗ x + 2 ∗ y + 0 ∗ z + 0 ∗ w 0 ∗ x + 0 ∗ y + 2 ∗ z + 0 ∗ w 0 ∗ x + 0 ∗ y + 0 ∗ z + 1 ∗ w ] = [ 2 ∗ x + 0 + 0 + 0 0 + 2 ∗ y + 0 + 0 0 + 0 + 2 ∗ z + 0 0 + 0 + 0 + 1 ∗ w ] = [ 2 ∗ x 2 ∗ y 2 ∗ z w ]

et la valeur de w ne change pas. Vous pouvez vous demander : que signifie le sens d'une mise à l'échelle d'une direction ? Eh bien, souvent, pas grand-chose. Généralement, on ne le fait pas sauf dans quelques (rares) cas où cela peut être pratique.

La matrice d'identité n'est qu'un cas spécifique des matrices de redimensionnement, avec (X, Y, Z) = (1, 1, 1). C'est aussi un cas spécifique des matrices de translation avec (X, Y, Z) = (0, 0, 0).

En C++ :

Sélectionnez
// Utilisez #include <glm/gtc/matrix_transform.hpp> et #include <glm/gtx/transform.hpp>
glm::mat4 myScalingMatrix = glm::scale(2.0f, 2.0f ,2.0f);

II-E. Matrices de rotation

Elles sont assez compliquées. Je vais passer les détails ici, sachant qu'il n'est pas important de connaître leur fonctionnement pour un usage quotidien. Pour plus d'informations, jetez un œil à la FAQ sur les matrices (ou cette populaire FAQ sur les matrices et quaternions (en anglais)).

En C++ :

Sélectionnez
// Utilisez #include <glm/gtc/matrix_transform.hpp> et #include <glm/gtx/transform.hpp>
glm::vec3 myRotationAxis( ??, ??, ??);
glm::rotate( angle_en_degrees, myRotationAxis );

II-F. Accumuler les transformations

Voilà, on sait comment tourner, déplacer et redimensionner nos vecteurs. Cela serait bien si on pouvait combiner ces transformations. Cela est possible en multipliant les matrices ensemble, par exemple :

Sélectionnez
VecteurTransforme = MatrixTranslation * MatriceRotation * MatriceRedimensionnement * OriginalVecteur;

Cette ligne effectue la mise à l'échelle en premier, puis la rotation et finalement, la translation. C'est ainsi que la multiplication de matrice fonctionne.

Le résultat serait différent en écrivant ces opérations dans un autre ordre. Testez vous-même :

  • faites un pas en avant (attention à votre ordinateur) et tourner vers la gauche ;
  • tournez vers la gauche et faites un pas en avant.

En fait, l'ordre utilisé ci-dessus est celui que vous allez normalement utiliser pour les personnages et objets de votre jeu : la mise à l'échelle en premier, si besoin ; puis sa direction ; finalement la translation. Par exemple, pour un vaisseau (pour simplifier, les rotations ont été retirées) :

  • La mauvaise façon :

    • vous déplacez le vaisseau de (10, 0, 0). Son centre est maintenant à dix unités de l'origine ;
    • vous redimensionnez la taille de votre vaisseau d'un facteur 2. Toutes les coordonnées sont multipliées par 2 par rapport à l'origine, qui est très loin… finalement vous avez un gros vaisseau, mais centré à 2*20 = 20. Chose que vous ne voulez pas.

  • La bonne façon :

    • vous redimensionnez la taille de votre vaisseau d'un facteur 2. Vous obtenez un gros vaisseau, centré sur l'origine ;
    • vous déplacez votre vaisseau. Il conserve la même taille et il est au bon endroit.

Les multiplications matrices par matrices sont très proches des multiplications matrices par vecteur, donc encore une fois, je vais passer quelques détails et vous rediriger sur la FAQ des matrices si nécessaire. Pour le moment, on demande simplement à l'ordinateur de le faire :

En C++ avec GLM :

Sélectionnez
glm::mat4 myModelMatrix = myTranslationMatrix * myRotationMatrix * myScaleMatrix;
glm::vec4 myTransformedVector = myModelMatrix * myOriginalVector;

En GLSL :

Sélectionnez
mat4 transform = mat2 * mat1;
vec4 out_vec = transform * in_vec;

III. Les matrices de modèle, de vue et de projection

Pour la suite du tutoriel, on supposera savoir comment dessiner le modèle 3D favori de Blender : le singe Suzanne.

Les matrices de modèle, de vue et de projection sont des outils pratiques pour différencier proprement les transformations. Vous pouvez ne pas les utiliser (après tout, c'est ce que l'on a fait dans les deux premiers tutoriels). Mais vous devriez. C'est la façon dont tout le monde fait, car cela est une approche propre, comme on va le voir.

III-A. La matrice de modèle

Le modèle, tout comme notre triangle rouge adoré, est défini par un ensemble de sommets. Les coordonnées X, Y, Z de ces sommets sont définies par rapport au centre de l'objet : ce qui veut dire que si un vertex est en (0, 0, 0), il est au centre de l'objet.

Exemple de Suzanne centrée à l'origine

On aimerait pouvoir déplacer cet objet, peut-être car le joueur le contrôle avec le clavier et la souris. Facile, vous devez simplement apprendre à le faire : déplacement * rotation * redimensionnement, et c'est fini. Vous appliquez cette matrice sur tous vos sommets à chaque image (en GLSL, pas en C++) et tout bouge. Quelque chose qui ne bouge pas sera au centre du monde.

Suzanne dans le monde

Vos sommets sont maintenant dans l'espace monde. C'est la signification de la flèche noire dans l'image ci-dessous : on s'est déplacé de l'espace du modèle (tous les sommets sont définis par rapport au centre du modèle) à l'espace monde (tous les sommets sont définis par rapport au centre du monde).

Passage de l'espace du modèle à l'espace monde

Nous pouvons résumer cela avec le diagramme suivant :

Diagramme du passage de l'espace objet au l'espace monde

III-B. La matrice de vue

Voici une nouvelle fois la citation de Futurama :

Les moteurs ne déplacent pas du tout le vaisseau. Le vaisseau reste où il est et les moteurs déplacent l'univers autour de lui.
La caméra et Suzanne

Lorsque vous y pensez, la même chose s'applique aux caméras. Si vous souhaitez voir une montagne à partir d'un autre angle, vous pouvez déplacer soit la caméra… soit la montagne. Bien que cela ne soit pas possible dans la vraie vie, c'est très simple et pratique dans le monde de l'infographie.

Au début, votre caméra est à l'origine de l'espace monde. Afin de déplacer le monde, vous introduisez une nouvelle matrice, tout simplement. Imaginez que vous voulez déplacer la caméra de trois unités vers la droite (+X). C'est équivalent à déplacer l'ensemble du monde (les modèles inclus) trois unités sur la GAUCHE ! (-X). Pendant que votre cerveau fond, voici comment faire :

Sélectionnez
// Utilisez #include <glm/gtc/matrix_transform.hpp> et #include <glm/gtx/transform.hpp>
glm::mat4 ViewMatrix = glm::translate(-3.0f, 0.0f ,0.0f);

Encore une fois, l'image ci-dessous illustre ce phénomène : on s'est déplacé de l'espace monde (tous les sommets sont définis par rapport au centre du monde, comme nous l'avions fait dans la section précédente) vers l'espace caméra (tous les sommets sont définis par rapport à la caméra).

Passage de l'espace monde à l'espace caméra

Avant que votre tête n'explose, profitez de la superbe fonction glm::LookAt de GLM :

Sélectionnez
glm::mat4 CameraMatrix = glm::LookAt(
    cameraPosition, // la position de votre caméra, dans l'espace monde
    cameraTarget,   // l'endroit que vous souhaitez regarder, dans l'espace monde
    upVector        // certainement glm::vec3(0,1,0), mais (0,-1,0) vous fera voir à l'envers, ce qui peut être tout aussi bien
);

Voici le diagramme obligatoire :

Diagramme de transition de l'espace modèle à l'espace caméra

Ce n'est pas encore fini.

III-C. La matrice de projection

On est maintenant dans l'espace caméra. Cela signifie qu'après toutes ces transformations, un sommet ayant les coordonnées x == 0 et y == 0 devrait être affiché au centre de l'écran. Mais on ne peut pas utiliser uniquement les coordonnées x et y pour déterminer où un objet devrait être placé à l'écran : sa distance par rapport à la caméra (z) est aussi importante ! Pour deux sommets avec les même coordonnées x et y, le sommet avec la plus grande coordonnée z sera plus au centre de l'écran que l'autre.

Cela s'appelle la perspective :

Modèle transformé dans l'espace homogène

Et heureusement pour nous, cette projection peut être représentée par une matrice 4 x 4 (1) :

Sélectionnez
// Génère une matrice très compliquée à lire, mais néanmoins une matrice 4x4 standard
glm::mat4 projectionMatrix = glm::perspective(
    FoV,         // Le champ de vision horizontal, en degrés : la valeur de « zoom ». Pensez « lentille de caméra ». Habituellement entre 90° (très large) et 30° (assez zoomé)
    4.0f / 3.0f, // Ratio d'aspect. Dépend de la taille de votre fenêtre. Notez que 4/3 == 800/600 == 1280/960, cela vous dit quelque chose ?
    0.1f,        // Plan de découpe proche. Gardez-la la plus grande possible, ou vous allez avoir des problèmes de précision.
    100.0f       // Plan de découpe lointain. Gardez-la aussi petite que possible.
);

Une dernière fois :

On s'est déplacé de l'espace caméra (tous les sommets sont définis par rapport à la caméra) vers l'espace homogène (tous les sommets sont définis dans un petit cube). Tout ce qui est dans le cube apparaîtra à l'écran.

Et voici le diagramme final :

Diagramme de transformation des points

Voici un nouveau schéma afin que vous compreniez mieux ce qui se passe avec la projection. Avant la projection, nous avons les objets en bleu, dans l'espace caméra, et la forme rouge représente le champ de la caméra : la partie de la scène que la caméra est capable de voir.

Représentation du champ de la caméra dans la scène

Et voici ce que l'on obtient en multipliant tout par la matrice de projection :

Vision homogène de la scène

Dans cette image, le champ est devenu un cube parfait (entre -1 et 1 sur tous les axes, même si c'est un peu dur à voir) et tous les objets en bleu ont été déformés de la même façon. Donc, les objets proches de la caméra (= proche de la face du cube que nous ne pouvons pas voir) sont gros, les autres sont plus petits. Cela ressemble à la réalité !

Voici à quoi cela ressemble lorsque l'on est à « l'intérieur » du champ :

Vision de la caméra

C'est l'image que vous obtenez ! C'est simplement un peu trop carré, donc une autre transformation mathématique est appliquée pour correspondre à la taille de la fenêtre :

Image finale correspondant à la taille de la fenêtre

Et c'est cette image qui est affichée !

III-D. Accumulation des transformations : la matrice ModelViewProjection

… une simple multiplication de matrices comme vous les aimez déjà :

Sélectionnez
// C++ : calcul de la matrice
glm::mat4 MVPmatrix = projection * view * model; // Souvenez-vous : c'est inversé !
Sélectionnez
// GLSL : application de la matrice
transformed_vertex = MVP * in_vertex;

IV. Rassembler le tout

  • Première étape : générer la matrice ModelViewProjection (MVP). Cela doit être fait pour chaque modèle que vous affichez.

    Sélectionnez
    // Matrice de projection : Champ de vision de 45° , ration 4:3, distance d'affichage : 0.1 unités <-> 100 unités
glm::mat4 Projection = glm::perspective(45.0f, 4.0f / 3.0f, 0.1f, 100.0f);
// Matrice de la caméra
glm::mat4 View       = glm::lookAt(
    glm::vec3(4,3,3), // La caméra est à (4,3,3), dans l'espace monde
    glm::vec3(0,0,0), // et regarde l'origine
    glm::vec3(0,1,0)  // La tête est vers le haut (utilisez 0,-1,0 pour regarder à l'envers)
);
// Matrice de modèle : une matrice d'identité (le modèle sera à l'origine)
glm::mat4 Model      = glm::mat4(1.0f);  // Changez pour chaque modèle !
// Notre matrice ModelViewProjection : la multiplication des trois  matrices
glm::mat4 MVP        = Projection * View * Model; // Souvenez-vous, la multiplication de matrice fonctionne dans l'autre sens

  • Seconde étape : passez les matrices au GLSL

    Sélectionnez
    // Obtient un identifiant pour notre variable uniforme "MVP".
// Seulement au moment de l'initialisation.
GLuint MatrixID = glGetUniformLocation(programID, "MVP");

// Envoie notre transformation au shader actuel dans la variable uniforme "MVP"
// Pour chaque modèle affiché, comme la MVP sera différente (au moins pour la partie M)
glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]);

  • Troisième étape : utilisation des matrices pour transformer les sommets dans le GLSL.

    Sélectionnez
    in vec3 vertexPosition_modelspace;
uniform mat4 MVP;

void main(){

    // Obtient la position du sommet, dans l'espace de découpe : MVP * position
    vec4 v = vec4(vertexPosition_modelspace,1); // Transforme un vecteur 4D homogène, vous vous souvenez ?
    gl_Position = MVP * v;
}
  • Fini ! Voici le triangle du second tutoriel, toujours à l'origine (0, 0, 0), mais vu en perspective à partir du point (4, 3, 3), la tête en haut (0, 1, 0), avec un champ de vision de 45°.
Triangle final en perspective

Dans le sixième tutoriel vous allez apprendre à modifier ces valeurs dynamiquement à l'aide du clavier et de la souris pour créer une caméra comme dans les jeux, mais avant, on doit apprendre comment donner quelques couleurs à nos modèles 3D ( tutoriel 4) et des textures ( tutoriel 5).


OpenGL Moderne - tutoriel 4 : un cube coloré

Vu dans ... https://opengl.developpez.com/tutoriels/opengl-tutorial/4-cube-colore/

Un cube coloré

I. Introduction

Bienvenue dans le quatrième tutoriel ! Dans celui-ci, vous allez réaliser les choses suivantes :

  • afficher un cube au lieu d'un ennuyeux triangle ;
  • ajouter de jolies couleurs ;
  • apprendre ce qu'est le tampon de profondeur (Z-buffer).

II. Afficher un cube

Un cube possède six faces carrées. Comme OpenGL ne connaît que les triangles, on doit dessiner douze triangles : deux pour chaque face. On définit les sommets de la même façon que pour le triangle.

Sélectionnez
// Les sommets. Trois floats consécutifs donnent un sommet 3D ; trois sommets consécutifs donnent un triangle.
// Un cube possède six faces avec deux triangles pour chaque, donc cela fait 6*2=12 triangles et 12*3=36 sommets
static const GLfloat g_vertex_buffer_data[] = {
    -1.0f,-1.0f,-1.0f, // triangle 1 : début
    -1.0f,-1.0f, 1.0f,
    -1.0f, 1.0f, 1.0f, // triangle 1 : fin
    1.0f, 1.0f,-1.0f, // triangle 2 : début
    -1.0f,-1.0f,-1.0f,
    -1.0f, 1.0f,-1.0f, // triangle 2 : fin
    1.0f,-1.0f, 1.0f,
    -1.0f,-1.0f,-1.0f,
    1.0f,-1.0f,-1.0f,
    1.0f, 1.0f,-1.0f,
    1.0f,-1.0f,-1.0f,
    -1.0f,-1.0f,-1.0f,
    -1.0f,-1.0f,-1.0f,
    -1.0f, 1.0f, 1.0f,
    -1.0f, 1.0f,-1.0f,
    1.0f,-1.0f, 1.0f,
    -1.0f,-1.0f, 1.0f,
    -1.0f,-1.0f,-1.0f,
    -1.0f, 1.0f, 1.0f,
    -1.0f,-1.0f, 1.0f,
    1.0f,-1.0f, 1.0f,
    1.0f, 1.0f, 1.0f,
    1.0f,-1.0f,-1.0f,
    1.0f, 1.0f,-1.0f,
    1.0f,-1.0f,-1.0f,
    1.0f, 1.0f, 1.0f,
    1.0f,-1.0f, 1.0f,
    1.0f, 1.0f, 1.0f,
    1.0f, 1.0f,-1.0f,
    -1.0f, 1.0f,-1.0f,
    1.0f, 1.0f, 1.0f,
    -1.0f, 1.0f,-1.0f,
    -1.0f, 1.0f, 1.0f,
    1.0f, 1.0f, 1.0f,
    -1.0f, 1.0f, 1.0f,
    1.0f,-1.0f, 1.0f
};

Le tampon OpenGL est créé, lié, rempli et configuré avec les fonctions de base (glGenBuffers, glBindBuffer, glBufferData, glVertexAttribPointer) ; lisez le deuxième tutoriel pour un rappel rapide. L'appel pour l'affichage ne change pas non plus, vous avez simplement à donner le bon nombre de sommets à dessiner :

Sélectionnez
// Affiche le triangle !
glDrawArrays(GL_TRIANGLES, 0, 12*3); // 12*3 indices démarrant à 0 -> 12 triangles -> 6 carrés

Quelques remarques sur ce code :

  • pour l'instant, nos modèles 3D sont fixes : afin de les modifier, vous devez changer le code source, recompiler l'application et prier que tout aille bien. On apprendra comment charger dynamiquement les modèles dans le septième tutoriel ;
  • chaque sommet est écrit au moins trois fois (cherchez la ligne « -1.0f,-1.0f,-1.0f » dans le code ci-dessus). C'est une incroyable perte de mémoire. On apprendra comment gérer cela dans le neuvième tutoriel.

Vous avez maintenant tous les morceaux pour dessiner le cube en blanc. Faites fonctionner les shaders ! Allez, ou au moins, essayez :).

III. Ajouter des couleurs

Une couleur est, conceptuellement, exactement identique à une position : ce ne sont que des données. Avec les mots d'OpenGL, ce sont des « attributs ». En fait, on les a déjà utilisés avec glEnableVertexAttribArray() et glVertexAttribPointer(). On ajoute un autre attribut à chaque sommet. Le code va ressembler à celui pour les positions.

Premièrement, la déclaration des couleurs : un triplet RGB par sommet. Ici, j'en ai généré quelques-uns aléatoirement, donc le résultat ne sera pas beau, mais vous pouvez faire mieux, par exemple en copiant la position des sommets pour définir sa couleur.

Sélectionnez
// Une couleur pour chaque sommet. Elles ont été générées aléatoirement.
static const GLfloat g_color_buffer_data[] = {
    0.583f,  0.771f,  0.014f,
    0.609f,  0.115f,  0.436f,
    0.327f,  0.483f,  0.844f,
    0.822f,  0.569f,  0.201f,
    0.435f,  0.602f,  0.223f,
    0.310f,  0.747f,  0.185f,
    0.597f,  0.770f,  0.761f,
    0.559f,  0.436f,  0.730f,
    0.359f,  0.583f,  0.152f,
    0.483f,  0.596f,  0.789f,
    0.559f,  0.861f,  0.639f,
    0.195f,  0.548f,  0.859f,
    0.014f,  0.184f,  0.576f,
    0.771f,  0.328f,  0.970f,
    0.406f,  0.615f,  0.116f,
    0.676f,  0.977f,  0.133f,
    0.971f,  0.572f,  0.833f,
    0.140f,  0.616f,  0.489f,
    0.997f,  0.513f,  0.064f,
    0.945f,  0.719f,  0.592f,
    0.543f,  0.021f,  0.978f,
    0.279f,  0.317f,  0.505f,
    0.167f,  0.620f,  0.077f,
    0.347f,  0.857f,  0.137f,
    0.055f,  0.953f,  0.042f,
    0.714f,  0.505f,  0.345f,
    0.783f,  0.290f,  0.734f,
    0.722f,  0.645f,  0.174f,
    0.302f,  0.455f,  0.848f,
    0.225f,  0.587f,  0.040f,
    0.517f,  0.713f,  0.338f,
    0.053f,  0.959f,  0.120f,
    0.393f,  0.621f,  0.362f,
    0.673f,  0.211f,  0.457f,
    0.820f,  0.883f,  0.371f,
    0.982f,  0.099f,  0.879f
};

Le tampon est créé, lié et rempli de la même façon que le précédent :

Sélectionnez
GLuint colorbuffer;
glGenBuffers(1, &colorbuffer);
glBindBuffer(GL_ARRAY_BUFFER, colorbuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(g_color_buffer_data), g_color_buffer_data, GL_STATIC_DRAW);

La configuration est identique, elle aussi :

Sélectionnez
// second tampon d'attributs : couleurs
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, colorbuffer);
glVertexAttribPointer(
    1,                                // attribut. Aucune raison particulière pour 1, mais cela doit correspondre au « layout » du shader
    3,                                // taille
    GL_FLOAT,                         // type
    GL_FALSE,                         // normalisé ?
    0,                                // nombre d'octets séparant deux sommets dans le tampon
    (void*)0                          // décalage du tableau de tampons
);

Maintenant, dans le vertex shader, on accède au nouveau tampon :

Sélectionnez
// Notez que le "1" ici correspond au "1" dans glVertexAttribPointer
layout(location = 1) in vec3 vertexColor;

Dans ce cas, on ne fait rien de fantaisiste dans le vertex shader. On envoie simplement la couleur au fragment shader :

Sélectionnez
// Données de sortie ; sera interpolée pour chaque fragment
out vec3 fragmentColor;

void main(){

    [...]

    // La couleur de chaque sommet sera interpolée
    // pour produire la couleur de chaque fragment
    fragmentColor = vertexColor;
}

Dans le fragment shader, vous déclarez encore une fois fragmentColor :

Sélectionnez
// Valeurs interpolées à partir du vertex shader
in vec3 fragmentColor;

… et la copie dans la couleur finale de sortie :

Sélectionnez
// Données de sortie
out vec3 color;

void main(){
    // Output color = color spécifié dans le vertex shader
    // interpolé entre les trois sommets alentours
    color = fragmentColor;
}

Et voici ce que l'on obtient :

Un cube sans tampon de profondeur

Argh. Horrible. Pour comprendre ce qui se passe, voici un schéma de ce qui se produit lorsque vous dessinez un triangle « au loin » et un triangle « proche » :

Triange proche dessiné après le triangle au loin

Cela semble ok. Maintenant le triangle « au loin » en deuxième :

Triange au loin dessiné après le triangle proche

Il recouvre le triangle « proche » même s'il est supposé être derrière ! C'est ce qui se passe avec notre cube : certaines faces sont supposées être cachées, mais comme elles sont dessinées après, elles sont visibles. Le tampon de profondeur (Z-buffer) va venir à la rescousse !

Si vous ne voyez pas le problème, changez la position de votre caméra à (4,3,-3)

Si « la couleur est comme une position, c'est un attribut », pourquoi doit-on déclarer out vec3 fragmentColor ;et in vec3 fragmentColor ; pour la couleur et pas pour la position ? Car la position est un peu spéciale : c'est la seule qui est obligatoire (sinon OpenGL ne saurait pas où dessiner le triangle !). Donc, dans le vertex shader, gl_Position est une variable intégrée du langage.

IV. Le tampon de profondeur

La solution à ce problème est de conserver la profondeur ('Z') de chaque fragment dans un tampon et pour toutes les fois où vous voulez écrire un fragment, vous vérifiez d'abord si vous le pouvez (si le nouveau fragment est plus proche que l'ancien).

Vous pouvez le faire vous-même, mais c'est beaucoup plus simple de demander au matériel de le faire :

Sélectionnez
// Active le test de profondeur
glEnable(GL_DEPTH_TEST);
// Accepte le fragment s'il est plus proche de la caméra que le précédent accepté
glDepthFunc(GL_LESS);

Vous devez aussi nettoyer le tampon de profondeur à chaque image, au lieu de ne le faire que pour la couleur :

Sélectionnez
// Nettoie l'écran
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

Et cela suffit pour régler tous vos problèmes.

Un cube coloré normal

V. Exercices

  • Dessinez le cube ET le triangle à deux emplacements différents. Vous aurez besoin de générer deux matrices MVP, de faire deux appels pour l'affichage dans la boucle principale, mais seulement un shader est nécessaire.

  • Générez les valeurs des couleurs vous-même. Quelques idées : aléatoirement, de manière à ce que les couleurs changent à chaque exécution ; dépendantes de la position des sommets ; un mélange des deux ; quelques idées créatives :). Au cas où vous ne connaîtriez pas la syntaxe C++, la voici :

    Sélectionnez
    static GLfloat g_color_buffer_data[12*3*3];
for (int v = 0; v < 12*3 ; v++){
    g_color_buffer_data[3*v+0] = votre couleur rouge ici ;
    g_color_buffer_data[3*v+1] = votre couleur verte ici ;
    g_color_buffer_data[3*v+2] = votre couleur bleu ici ;
}
  • Une fois que vous avez fait cela, modifiez les couleurs à chaque affichage. Vous devez appeler glBufferData à chaque image. Assurez-vous que le tampon approprié est lié (glBindBuffer).


OpenGL Moderne - tutoriel 5 : un cube texturé

Vu dans ... https://opengl.developpez.com/tutoriels/opengl-tutorial/5-un-cube-texture/

un cube texturé

I. Introduction

Dans ce tutoriel, vous allez apprendre :

  • ce que sont les coordonnées UV ;
  • comment charger une texture vous-même ;
  • comment les utiliser dans OpenGL ;
  • ce que sont le filtrage et les MIP maps et comment les utiliser ;
  • comment charger une texture plus efficacement avec GLFW.

II. À propos des coordonnées UV

Lors de l'application d'une texture sur un modèle, vous avez besoin d'une méthode pour indiquer à OpenGL quelle partie de l'image doit être utilisée pour chaque triangle. Cela se fait grâce aux coordonnées UV.

Chaque sommet possède, en plus de sa position, une paire de floats, U et V. Ces coordonnées sont utilisées pour accéder à la texture de la manière suivante :

Schéma d'application d'une texture sur un triangle

Remarquez comment la texture est déformée sur le triangle.

III. Charger des images .BMP vous-même

Connaître le format de fichier BMP n'est pas important : nombreuses sont les bibliothèques pouvant le faire pour vous. Mais il est très simple et il peut vous aider à comprendre comment les choses fonctionnent en interne. Donc, on va écrire un chargeur de fichier BMP à partir de rien, afin que vous sachiez comment il fonctionne et ne plus jamais l'utiliser.

Voici la déclaration de la fonction de chargement :

Sélectionnez
GLuint loadBMP_custom(const char * imagepath);

Et elle s'utilise comme ça :

Sélectionnez
GLuint image = loadBMP_custom("./my_texture.bmp");

Maintenant, comment lire un fichier BMP ?

Premièrement, on a besoin de quelques données. Ces variables seront définies lors de la lecture du fichier.

Sélectionnez
// Données lues à partir de l'en-tête du fichier BMP
unsigned char header[54]; // Chaque fichier BMP débute par un en-tête de 54 octets
unsigned int dataPos;     // Position dans le fichier où les données débutent
unsigned int width, height;
unsigned int imageSize;   // = width*height*3
// les données RBG
unsigned char * data;

On doit maintenant ouvrir le fichier :

Sélectionnez
// Ouverture du fichier
FILE * file = fopen(imagepath,"rb");
if (!file)                              {printf("Image could not be opened\n"); return 0;}

La première chose dans le fichier est un en-tête de 54 octets. Il contient les informations telles que « est-ce vraiment un fichier BMP ? », la taille de l'image, le nombre de bits par pixel, etc. Donc, on lit l'en-tête :

Sélectionnez
if ( fread(header, 1, 54, file)!=54 ){ // S'il n'est pas possible de lire 54 octets : problème
    printf("Not a correct BMP file\n");
    return false;
}

L'en-tête démarre toujours avec BM. En fait, voici ce que vous obtenez lorsque vous ouvrez un fichier .BMP dans un éditeur hexadécimal :

Vue d'un BMP dans un éditeur hexadécimal

Donc, on doit vérifier si les deux premiers octets valent vraiment 'B' et 'M' :

Sélectionnez
if ( header[0]!='B' || header[1]!='M' ){
    printf("Not a correct BMP file\n");
    return 0;
}

Maintenant, on peut lire la taille de l'image, l'emplacement des données dans le fichier, etc :

Sélectionnez
// Lit des entiers à partir du tableau d'octets
dataPos    = *(int*)&(header[0x0A]);
imageSize  = *(int*)&(header[0x22]);
width      = *(int*)&(header[0x12]);
height     = *(int*)&(header[0x16]);

On doit générer des informations pour les cas où elles sont manquantes :

Sélectionnez
// Certains fichiers BMP sont mal formés, on devine les informations manquantes
if (imageSize==0)    imageSize=width*height*3; // 3 : un octet pour chaque composante rouge, vert et bleu
if (dataPos==0)      dataPos=54; // l'en-tête BMP est fait de cette façon

Maintenant que la taille de l'image est connue, on peut allouer de la mémoire pour la remplir avec l'image lue :

Sélectionnez
// Crée un tampon
data = new unsigned char [imageSize];

// Lit les données à partir du fichier pour les mettre dans le tampon
fread(data,1,imageSize,file);

// Tout est en mémoire maintenant, le fichier peut être fermé
fclose(file);

On arrive à la section OpenGL. La création de textures est très similaire à la création de tampons : créer une texture, la lier, la remplir et la configurer.

Dans glTexImage2D, GL_RGB indique que l'on utilise une couleur ayant trois composantes et GL_BGR indique comment les données sont réellement disposées en mémoire. En fait, le fichier BMP ne stocke pas les pixels en Rouge->Vert->Bleu mais Bleu->Vert->Rouge, donc nous en devons informer OpenGL.

Sélectionnez
// Crée une texture OpenGL
GLuint textureID;
glGenTextures(1, &textureID);

// "Lie" la nouvelle texture : toutes les fonctions agissant sur les textures suivantes vont modifier cette texture
glBindTexture(GL_TEXTURE_2D, textureID);

// Donne l'image à OpenGL
glTexImage2D(GL_TEXTURE_2D, 0,GL_RGB, width, height, 0, GL_BGR, GL_UNSIGNED_BYTE, data);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

On va expliquer les deux dernières lignes plus tard. Dorénavant, du côté C++, vous pouvez utiliser la nouvelle fonction pour charger une texture :

Sélectionnez
GLuint Texture = loadBMP_custom("uvtemplate.bmp");

Il est important d'utiliser des textures ayant des tailles en puissances de deux :

  • correct : 128 * 128, 256 * 256, 1024 * 1024, 2 * 2… ;
  • mauvais : 127 * 128, 3 * 5… ;
  • correct mais étrange : 128 * 256…

IV. Utiliser une texture dans OpenGL

On commence par le fragment shader. Il est globalement simple :

Sélectionnez
#version 330 core

// Valeurs interpolées à partir du vertex shader
in vec2 UV;

// Données en sortie
out vec3 color;

// Valeurs qui sont constantes pour l'ensemble du modèle.
uniform sampler2D myTextureSampler;

void main(){

    // Couleur de sortie = couleur de la texture pour les coordonnées UV spécifiées
    color = texture( myTextureSampler, UV ).rgb;
}

Trois choses :

  • le fragment shader a besoin des coordonnées UV. Cela semble logique ;
  • il a aussi besoin d'un « sampler2D » pour savoir à quelle texture accéder (vous pouvez accéder à plusieurs textures dans le même shader) ;
  • finalement, l'accès à la texture est effectué avec texture(), renvoyant un vec4 contenant (R,G,B,A). On va voir ce qu'est le A, prochainement.

Le vertex shader est simple aussi, vous devez juste passer les coordonnées UV au fragment shader :

Sélectionnez
#version 330 core

// Données d'entrée du sommet, diffèrent pour chaque exécution de ce shader.
layout(location = 0) in vec3 vertexPosition_modelspace;
layout(location = 1) in vec2 vertexUV;

// Données de sortie ; vont être interpolées pour chaque fragment.
out vec2 UV;

// Valeurs constantes pour l'ensemble du modèle.
uniform mat4 MVP;

void main(){

    // Position de sortie du sommet, dans l'espace de découpe : MVP * position
    gl_Position = MVP * vec4(vertexPosition_modelspace,1);

    // Coordonnées UV du sommet. Pas d'espace spécifique de coordonnées pour celles-ci.
    UV = vertexUV;
}

Vous souvenez-vous du « layout(location = 1) in vec2 vertexUV » du quatrième tutoriel ? Eh bien, on doit faire exactement la même chose ici, mais au lieu de donner un tampon de triplets (R,G,B), nous allons donner une paire (U,V).

Sélectionnez
// Deux coordonnées UV pour chaque sommet. Les coordonnées ont été créées avec Blender. Vous allez bientôt apprendre comment les générer vous-même.
static const GLfloat g_uv_buffer_data[] = {
    0.000059f, 1.0f-0.000004f,
    0.000103f, 1.0f-0.336048f,
    0.335973f, 1.0f-0.335903f,
    1.000023f, 1.0f-0.000013f,
    0.667979f, 1.0f-0.335851f,
    0.999958f, 1.0f-0.336064f,
    0.667979f, 1.0f-0.335851f,
    0.336024f, 1.0f-0.671877f,
    0.667969f, 1.0f-0.671889f,
    1.000023f, 1.0f-0.000013f,
    0.668104f, 1.0f-0.000013f,
    0.667979f, 1.0f-0.335851f,
    0.000059f, 1.0f-0.000004f,
    0.335973f, 1.0f-0.335903f,
    0.336098f, 1.0f-0.000071f,
    0.667979f, 1.0f-0.335851f,
    0.335973f, 1.0f-0.335903f,
    0.336024f, 1.0f-0.671877f,
    1.000004f, 1.0f-0.671847f,
    0.999958f, 1.0f-0.336064f,
    0.667979f, 1.0f-0.335851f,
    0.668104f, 1.0f-0.000013f,
    0.335973f, 1.0f-0.335903f,
    0.667979f, 1.0f-0.335851f,
    0.335973f, 1.0f-0.335903f,
    0.668104f, 1.0f-0.000013f,
    0.336098f, 1.0f-0.000071f,
    0.000103f, 1.0f-0.336048f,
    0.000004f, 1.0f-0.671870f,
    0.336024f, 1.0f-0.671877f,
    0.000103f, 1.0f-0.336048f,
    0.336024f, 1.0f-0.671877f,
    0.335973f, 1.0f-0.335903f,
    0.667969f, 1.0f-0.671889f,
    1.000004f, 1.0f-0.671847f,
    0.667979f, 1.0f-0.335851f
};

Les coordonnées UV ci-dessus correspondent à ce modèle :

Coordonnées de texture dans Blender

Le reste est évident. Générer le tampon, le lier, le remplir, le configurer et dessiner le tampon de sommet comme d'habitude. Soyez prudent et utilisez 2 comme second paramètre (la taille) de la fonction glVertexAttribPointer au lieu de 3.

Voici le résultat :

Un cube texturé

et en zoomant un peu :

Zoom sur le cube texturé

V. Que sont le filtrage et les MIP maps et comment les utiliser

Comme vous pouvez le voir dans la capture ci-dessus, la qualité de la texture n'est pas superbe. Cela est dû à ce que l'on a écrit dans la fonction loadBMP_custom :

Sélectionnez
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

Cela signifie que dans notre fragment shader, la fonction texture() prend le texel qui est aux coordonnées (U, V) et continue joyeusement avec :

Schéma du filtrage nearest

Il y a de plusieurs choses que l'on peut faire pour améliorer cela.

V-A. Filtrage linéaire

Avec le filtrage linéaire, texture() regarde aussi les texels autour et mélange les couleurs suivant la distance de chaque centre. Cela évite les bordures nettes vues précédemment.

Schéma filtrage linéaire

C'est beaucoup mieux et c'est beaucoup utilisé, mais si vous voulez une meilleure qualité, vous pouvez aussi utiliser le filtrage anisotrope, qui est un peu plus lent.

V-B. Filtrage anisotrope

Celui-ci se rapproche de la partie de l'image qui est réellement vue dans le fragment. Par exemple, si la texture suivante est vue sur le côté et légèrement tournée, le filtrage anisotrope calculera la couleur contenue dans le rectangle bleu en prenant un nombre fixe d'échantillons (le niveau d'anisotropie) suivant sa direction principale :

Schéma du filtrage anisotrope

V-C. MIP maps

Les filtrages linéaire et anisotrope ont tous les deux un souci. Si la texture est vue de très loin, le mélange de quatre texels ne suffira pas. En fait, si votre modèle 3D est très loin et qu'il ne prend qu'un fragment sur l'écran, TOUS les texels de l'image vont être pris en compte pour calculer la moyenne afin de produire la couleur finale. Évidemment, cela n'est pas fait pour préserver les performances. À la place, on introduit les MIP maps :

Exemple de mipmaps (source Wikipedia)
Exemple de mipmap (source Wikipedia)
  • à la tuile d'initialisation, vous diminuez votre image d'un facteur 2, successivement, jusqu'à atteindre une image 1 x 1 (qui correspond à la moyenne de tous les texels de l'image) ;
  • lorsque vous dessinez un modèle, vous sélectionnez la MIP map la plus appropriée à utiliser suivant la taille à laquelle devrait être le texel ;
  • vous échantillonnez cette MIP map avec l'un des filtrages vus précédemment ;
  • pour une qualité supérieure, vous pouvez aussi échantillonner deux MIP maps et mélanger le résultat.

Heureusement, tout cela est très simple à faire, OpenGL sait le faire si vous lui demandez gentiment :

Sélectionnez
// Lorsque l'on agrandit l'image (aucune MIP map plus grande n'est disponible), utiliser le filtrage LINÉAIRE
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// Lorsque l'on rétrécit l'image, utiliser un fondu linéaire entre deux MIP maps, chacune étant aussi filtrée linéairement
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
// Générer les MIP maps.
glGenerateMipmap(GL_TEXTURE_2D);

VI. Charger les images avec GLFW

La fonction loadBMP_custom est bien, car on la fait à partir de rien. Mais l'utilisation d'une bibliothèque dédiée est mieux. GLFW peut aussi faire cela (mais uniquement pour les fichiers TGA) :

Le chargement des fichiers TGA dans GLFW est dépréciée depuis la version 3.

Sélectionnez
GLuint loadTGA_glfw(const char * imagepath){

    // Crée une texture OpenGL
    GLuint textureID;
    glGenTextures(1, &textureID);

    // "Lie" la nouvelle texture créée : tous les appels suivants aux fonctions de texture vont modifier cette texture
    glBindTexture(GL_TEXTURE_2D, textureID);

    // Lit le fichier, appel glTexImage2D avec les bons paramètres
    glfwLoadTexture2D(imagepath, 0);

    // Joli filtrage trilinéaire.
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glGenerateMipmap(GL_TEXTURE_2D);

    // Retourne l'identifiant de la texture que l'on vient de créer
    return textureID;
}

VII. Les textures compressées

À ce point, vous vous demandez probablement comment charger les fichiers JPEG à la place des TGA.

Réponse courte : ne le faites pas, il y a de meilleures options.

VII-A. Créer une texture compressée

  • Téléchargez l'outil d'ATI : The Compressonator.
  • Chargez une texture en puissance de deux avec.
  • Compressez-la en DXT1, DXT3 ou DXT5 (plus d'informations sur les différences des formats sur Wikipedia) :
    Outil ATI : Compressonator
  • Générez les MIP maps afin de ne pas avoir à le faire à l'exécution.
  • Exportez le résultat dans un fichier .DDS.

À ce moment, votre image est compressée dans un format qui est directement compatible avec le GPU. Pour n'importe quel appel à texture() dans un shader, le GPU décompressera la texture à la volée. Cela peut sembler lent, mais comme cela prend TELLEMENT moins de mémoire, moins de données ont besoin d'être transférées, sachant que les transferts mémoire sont lents, et que la décompression de texture est gratuite (il y a des puces dédiées à cela). Généralement, l'utilisation de la compression de texture augmente les performances de 20 %.

VII-B. Utiliser la texture compressée

Voici comment charger l'image. C'est très proche du code pour le BMP, sauf que l'en-tête est organisé différemment :

Sélectionnez
GLuint loadDDS(const char * imagepath){

    unsigned char header[124];

    FILE *fp;

    /* essaie d'ouvrir le fichier */
    fp = fopen(imagepath, "rb");
    if (fp == NULL)
        return 0;

    /* vérifie le type du fichier */
    char filecode[4];
    fread(filecode, 1, 4, fp);
    if (strncmp(filecode, "DDS ", 4) != 0) {
        fclose(fp);
        return 0;
    }

    /* récupère la description de la surface */
    fread(&header, 124, 1, fp);

    unsigned int height      = *(unsigned int*)&(header[8 ]);
    unsigned int width         = *(unsigned int*)&(header[12]);
    unsigned int linearSize     = *(unsigned int*)&(header[16]);
    unsigned int mipMapCount = *(unsigned int*)&(header[24]);
    unsigned int fourCC      = *(unsigned int*)&(header[80]);

Les données de l'image se trouvent après l'en-tête : tous les niveaux de MIP maps, les uns après les autres. On peut les lire en une fois :

Sélectionnez
    unsigned char * buffer;
    unsigned int bufsize;
    /* quelle va être la taille des données incluant les MIP maps ? */
    bufsize = mipMapCount > 1 ? linearSize * 2 : linearSize;
    buffer = (unsigned char*)malloc(bufsize * sizeof(unsigned char));
    fread(buffer, 1, bufsize, fp);
    /* fermer le pointeur de fichier */
    fclose(fp);

Ici, on doit gérer trois formats différents : DXT1, DXT3 et DXT5. On doit convertir l'indicateur « fourCC » en une valeur que comprend OpenGL.

Sélectionnez
    unsigned int components  = (fourCC == FOURCC_DXT1) ? 3 : 4;
    unsigned int format;
    switch(fourCC)
    {
    case FOURCC_DXT1:
        format = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
        break;
    case FOURCC_DXT3:
        format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
        break;
    case FOURCC_DXT5:
        format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
        break;
    default:
        free(buffer);
        return 0;
    }

La création de texture est effectuée comme d'habitude :

Sélectionnez
    // Crée une texture OpenGL
    GLuint textureID;
    glGenTextures(1, &textureID);

    // "Lie" la nouvelle texture : tous les futurs appels aux fonctions de texture vont modifier cette texture
    glBindTexture(GL_TEXTURE_2D, textureID);

Et maintenant, on peut remplir chaque MIP map l'une après l'autre :

Sélectionnez
    unsigned int blockSize = (format == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT) ? 8 : 16;
    unsigned int offset = 0;

    /* charge les MIP maps */
    for (unsigned int level = 0; level < mipMapCount && (width || height); ++level)
    {
        unsigned int size = ((width+3)/4)*((height+3)/4)*blockSize;
        glCompressedTexImage2D(GL_TEXTURE_2D, level, format, width, height,
            0, size, buffer + offset);

        offset += size;
        width  /= 2;
        height /= 2;
    }
    free(buffer);

    return textureID;


OpenGL Moderne - tutoriel 6 : clavier et souris

Vu dans ... https://opengl.developpez.com/tutoriels/opengl-tutorial/6-clavier-et-souris/

I. Introduction

clavier et souris

II. L'interface

Comme le code va être réutilisé dans les autres tutoriels, on va placer le code dans un fichier à part : common/controls.cpp et déclarer les fonctions dans common/controls.hpp afin que le fichier tutorial06.cpp puisse les utiliser.

Le code du fichier tutoriel06.cpp ne change pas beaucoup par rapport au tutoriel précédent. La plus grande modification est le calcul de la matrice MVP à chaque image au lieu de la calculer une unique fois. Déplacez donc ce code dans la boucle principale :

Sélectionnez
do{

    // ...

    // Calcul de la matrice MVP à partir des entrées clavier et souris
    computeMatricesFromInputs();
    glm::mat4 ProjectionMatrix = getProjectionMatrix();
    glm::mat4 ViewMatrix = getViewMatrix();
    glm::mat4 ModelMatrix = glm::mat4(1.0);
    glm::mat4 MVP = ProjectionMatrix * ViewMatrix * ModelMatrix;

    // ...
}

Ce code nécessite trois nouvelles fonctions :

  • computeMatricesFromInputs() lit le clavier et la souris et calcule les matrices de projection et de vue. C'est ici que la magie opère ;
  • getProjectionMatrix() retourne simplement la matrice de projection calculée ;
  • getViewMatrix() retourne simplement la matrice de vue calculée.

C'est une façon de faire. Bien sûr, si vous n'aimez pas ces fonctions, allez-y et changez-les.

Regardez maintenant ce qu'il y a dans controls.cpp.

III. Le vrai code

On a besoin de quelques variables :

Sélectionnez
// position
glm::vec3 position = glm::vec3( 0, 0, 5 );
// angle horizontal : vers -Z
float horizontalAngle = 3.14f;
// angle vertical : 0, regarde l'horizon
float verticalAngle = 0.0f;
// Champ de vision initial
float initialFoV = 45.0f;

float speed = 3.0f; // trois unités/seconde
float mouseSpeed = 0.005f;

Le champ de vision est le niveau de zoom. 80° = un grand angle donc de grosses déformations. 60° - 45° : standard. 20° : gros zoom.

Premièrement, on va recalculer la position, l'angle horizontal, l'angle vertical et le champ de vision selon les entrées, puis on va calculer les matrices de vue et de projection à partir de la position, de l'angle horizontal, de l'angle vertical et du champ de vision.

III-A. Orientation

Lire les informations de la souris est facile :

Sélectionnez
// Récupère la position de la souris
int xpos, ypos;
glfwGetMousePos(&xpos, &ypos);

Mais on doit prendre garde à replacer le curseur au centre de l'écran ou il ira en dehors de la fenêtre et vous ne serez plus capable de bouger.

Sélectionnez
// Réinitialise la position de la souris pour la prochaine image
glfwSetMousePos(1024/2, 768/2);

Ce code fait l'hypothèse que la fenêtre est de 1024 * 768, ce qui n'est évidemment pas toujours le cas. Si vous le souhaitez, vous pouvez utiliser glfwGetWindowSize pour récupérer la taille de la fenêtre.

Maintenant, on calcule nos angles de vision :

Sélectionnez
// Calcule la nouvelle orientation
horizontalAngle += mouseSpeed * deltaTime * float(1024/2 - xpos );
verticalAngle   += mouseSpeed * deltaTime * float( 768/2 - ypos );

On lit ces lignes de droite à gauche :

  • 1024/2 - xpos correspond à la distance de la souris par rapport au centre de la fenêtre. Plus grande est la valeur, plus nous voulons tourner ;
  • float(…) convertit la position en un nombre à virgule flottante afin que la multiplication soit exacte ;
  • mouseSpeed n'est là que pour ralentir ou accélérer les rotations. Modifiez cette valeur à volonté ou laissez l'utilisateur la choisir ;
  • += : si vous n'avez pas déplacé la souris, 1024/2 - xpos vaudra 0 et horizontalAngle += 0 ne modifie pas la variable. Si vous aviez un '=' à la place, vous seriez retourné de force à l'orientation d'origine et cela à chaque image, ce qui n'est pas ce que l'on veut.

Maintenant, on peut calculer un vecteur qui représente, dans l'espace monde, la direction vers laquelle on regarde.

Sélectionnez
// Direction : une conversion de coordonnées sphériques vers coordonnées cartésiennes
glm::vec3 direction(
    cos(verticalAngle) * sin(horizontalAngle),
    sin(verticalAngle),
    cos(verticalAngle) * cos(horizontalAngle)
);

C'est un calcul standard, mais si vous ne connaissez pas sinus et cosinus, voici une courte explication :

Explication de sinus et cosinus

La formule ci-dessus n'est qu'une généralisation pour la 3D.

Maintenant, on souhaite calculer le vecteur « haut » efficacement. Remarquez que le « haut » n'est pas toujours en direction de +Y : si vous regardez vers le bas, par exemple, le vecteur « haut » sera en réalité vertical. Voici un exemple avec deux caméras ayant la même position, la même direction, mais de vecteurs « haut » différents.

Dans ce cas, la seule constante est que le vecteur allant sur la droite est toujours horizontal. Vous pouvez vérifier cela en plaçant votre bras à l'horizontal et en regardant vers le haut, le bas et dans toutes les directions. Voici comment définir le « bon » vecteur « vers la droite » : sa coordonnée Y est 0, car le vecteur est horizontal et ses coordonnées X et Z sont exactement comme dans la figure ci-dessus, mais avec des angles tournés de 90 °, ou Pi / 2 radians.

Sélectionnez
// vecteur « vers la droite »
glm::vec3 right = glm::vec3(
    sin(horizontalAngle - 3.14f/2.0f),
    0,
    cos(horizontalAngle - 3.14f/2.0f)
);

On a le vecteur « vers la droite » et une « direction », ou un vecteur vers « l'avant ». Le vecteur « haut » est le vecteur qui est perpendiculaire à ceux-ci. Il y a un outil mathématique pratique qui rend cela très simple : le produit vectoriel.

Sélectionnez
// Vecteur « haut » : perpendiculaire à la direction et le vecteur vers la droite
glm::vec3 up = glm::cross( right, direction );

Pour se souvenir de ce que fait le produit vectoriel, c'est très simple. Rappelez-vous la règle de la main droite du troisième tutoriel. Le premier vecteur est le pouce ; le second l'index et le résultat est le majeur. C'est très pratique.

III-B. Position

Le code est facile à comprendre. D'ailleurs, j'ai utilisé les touches fléchées à la place de aswd, car je suis sur un clavier azerty et aswd correspond à zqsd. C'est aussi différent avec les claviers qwerZ et passons les claviers coréens. Je ne sais même pas quelle disposition les coréens ont, mais je suppose que c'est différent.

Sélectionnez
// Aller vers l'avant
if (glfwGetKey( GLFW_KEY_UP ) == GLFW_PRESS){
    position += direction * deltaTime * speed;
}
// Aller vers l'arrière
if (glfwGetKey( GLFW_KEY_DOWN ) == GLFW_PRESS){
    position -= direction * deltaTime * speed;
}
// Pas à droite
if (glfwGetKey( GLFW_KEY_RIGHT ) == GLFW_PRESS){
    position += right * deltaTime * speed;
}
// Pas à gauche
if (glfwGetKey( GLFW_KEY_LEFT ) == GLFW_PRESS){
    position -= right * deltaTime * speed;
}

La seule chose particulière est deltaTime. Vous ne souhaitez pas vous déplacer d'une unité chaque image pour la simple raison que :

  • si vous avez un ordinateur rapide et que vous exécutez le programme à 60 FPS, vous vous déplacerez de 60 * vitesse unités en une seconde ;
  • si vous avez un ordinateur lent et que vous exécutez le programme à 20 FPS, vous vous déplacerez de 20 * vitesse unités en une seconde.

Comme le fait d'avoir un ordinateur plus rapide n'est pas une raison pour se déplacer plus vite, vous devez faire dépendre la distance du « temps depuis la dernière image », ou « deltaTime ».

  • si vous avez un ordinateur rapide et que vous exécutez le programme à 60 FPS, vous vous déplacerez de 1 / 60 * vitesse unités en une image, donc 1 * vitesse en une seconde.
  • Si vous avez un ordinateur lent et que vous exécutez le programme à 20 FPS, vous vous déplacerez de 1 / 20 * vitesse unités en une image, donc 1 * vitesse en une seconde.

Ce qui est bien mieux. Le deltaTime est très facile à calculer :

Sélectionnez
double currentTime = glfwGetTime();
float deltaTime = float(currentTime - lastTime);

III-C. Champ de vision

Pour s'amuser, on peut aussi lier la roulette de la souris au champ de vision pour faire rapidement un zoom :

Sélectionnez
float FoV = initialFoV - 5 * glfwGetMouseWheel();

III-D. Calculer les matrices

Le calcul des matrices est maintenant très simple. On utilise les mêmes fonctions que précédemment mais avec les nouveaux paramètres.

Sélectionnez
// Matrice de projection : champ de vision 45°, ration 4:3, distance d'affiche : 0.1 unit <-> 100 units
ProjectionMatrix = glm::perspective(FoV, 4.0f / 3.0f, 0.1f, 100.0f);
// Matrice de vue
ViewMatrix       = glm::lookAt(
    position,           // La caméra est là
    position+direction, // et regarde ici : à la même position, plus la « direction »
    up                  // la tête est vers le haut (définir à 0,-1,0 pour voir à l'envers)
);

IV. Résultat

On peut déplace le cube avec le clavier et la souris

V. Backface culling

Maintenant que vous pouvez librement vous déplacer, si vous allez dans le cube, vous allez remarquer que les polygones sont toujours affichés. Cela peut sembler évident, mais cette remarque ouvre en réalité une possibilité d'optimisation. En effet, normalement dans les applications, vous n'êtes jamais dans un cube.

L'idée consiste à dire au GPU de vérifier si la caméra est derrière ou devant le triangle. Si elle est devant, afficher le triangle ; si elle est derrière, et que le modèle est fermé et que l'on n'est pas dans le modèle, alors il y aura un autre triangle devant et personne ne le remarquera, sauf que tout sera plus rapide : deux fois moins de triangles en moyenne !

Le mieux, c'est qu'il est très facile à calculer. Le GPU calcule la normale du triangle (en utilisant le produit scalaire, vous vous rappelez ?) et vérifie si la normale est orientée vers la caméra ou non.

Cela possède un coût malheureusement : l'orientation du triangle est implicite. Cela signifie que si vous inversez deux sommets dans votre tampon, vous allez probablement avoir un trou. Mais cela vaut généralement le petit travail supplémentaire. Souvent, vous n'avez qu'à cliquer sur « inverser les normales » dans votre logiciel de modélisation 3D (qui va, en réalité, inverser les sommets et donc les normales) et tout sera parfait.

Voici comment on active le backface culling :

Sélectionnez
// Sélectionne les triangles dont les normales ne sont pas vers la caméra
glEnable(GL_CULL_FACE);


OpenGL Moderne - tutoriel 7 : chargement d'un modèle

Vu dans ... https://opengl.developpez.com/tutoriels/opengl-tutorial/7-chargement-de-modele/

chargement d'un modèle

I. Introduction

Jusqu'à maintenant, on avait manuellement codé le cube dans le code source. Je suis sûr que vous êtes d'accord pour dire que c'était pénible et pas pratique.

Dans ce tutoriel, on apprendra à charger un modèle 3D à partir d'un fichier. On fera exactement comme on a fait pour les textures : on va écrire un simple et très limité chargeur et je vais vous donner quelques pistes vers des bibliothèques qui le font mieux.

Pour garder ce tutoriel aussi simple que possible, on utilisera le format de fichier OBJ, qui est à la fois simple et très commun. Une fois encore, pour garder les choses simples, on gérera les fichiers OBJ n'ayant qu'une coordonnée UV et qu'une normale par sommet (vous n'avez pas à savoir ce qu'est une normale pour le moment).

II. Charger le fichier OBJ

La fonction, située dans common/objloader.cpp et déclarée dans common/objloader.hpp aura la signature suivante :

Sélectionnez
bool loadOBJ(
    const char * path,
    std::vector < glm::vec3 > & out_vertices,
    std::vector < glm::vec2 > & out_uvs,
    std::vector < glm::vec3 > & out_normals
)

On souhaite que loadOBJ lise le fichier « path », écrive les données dans out_vertices/out_uvs/out_normals et retourne false si quelque chose s'est mal passé. std::vector est la façon de déclarer un tableau de glm::vec3 en C++ dont la taille peut être modifiée à volonté : cela n'a rien à voir avec un vecteur mathématique. Sincèrement, ce n'est qu'un tableau. Finalement, le & signifie que la fonction sera capable de modifier les std::vector.

II-A. Exemple de fichier OBJ

Un fichier OBJ ressemble plus ou moins à ceci :

Sélectionnez
# Blender3D v249 OBJ File: untitled.blend
# www.blender3d.org
mtllib cube.mtl
v 1.000000 -1.000000 -1.000000
v 1.000000 -1.000000 1.000000
v -1.000000 -1.000000 1.000000
v -1.000000 -1.000000 -1.000000
v 1.000000 1.000000 -1.000000
v 0.999999 1.000000 1.000001
v -1.000000 1.000000 1.000000
v -1.000000 1.000000 -1.000000
vt 0.748573 0.750412
vt 0.749279 0.501284
vt 0.999110 0.501077
vt 0.999455 0.750380
vt 0.250471 0.500702
vt 0.249682 0.749677
vt 0.001085 0.750380
vt 0.001517 0.499994
vt 0.499422 0.500239
vt 0.500149 0.750166
vt 0.748355 0.998230
vt 0.500193 0.998728
vt 0.498993 0.250415
vt 0.748953 0.250920
vn 0.000000 0.000000 -1.000000
vn -1.000000 -0.000000 -0.000000
vn -0.000000 -0.000000 1.000000
vn -0.000001 0.000000 1.000000
vn 1.000000 -0.000000 0.000000
vn 1.000000 0.000000 0.000001
vn 0.000000 1.000000 -0.000000
vn -0.000000 -1.000000 0.000000
usemtl Material_ray.png
s off
f 5/1/1 1/2/1 4/3/1
f 5/1/1 4/3/1 8/4/1
f 3/5/2 7/6/2 8/7/2
f 3/5/2 8/7/2 4/8/2
f 2/9/3 6/10/3 3/5/3
f 6/10/4 7/6/4 3/5/4
f 1/2/5 5/1/5 2/9/5
f 5/1/6 6/10/6 2/9/6
f 5/1/7 8/11/7 6/10/7
f 8/11/7 7/12/7 6/10/7
f 1/2/8 2/9/8 3/13/8
f 1/2/8 3/13/8 4/14/8

Soit :

  • # est un commentaire, tout comme // en C++ ;
  • usemtl et mtllib décrivent l'aspect du modèle. On ne les utilise pas de ce tutoriel ;
  • v est un sommet ;
  • vt est une coordonnée de texture pour un sommet ;
  • vn est la normale pour un sommet ;
  • f est une face.

v, vt et vn sont simples à comprendre. f est un peu plus compliqué. Pour f 8/11/7 7/12/7 6/10/7 :

  • 8/11/7 décrit le premier sommet du triangle ;
  • 7/12/7 décrit le second sommet du triangle ;
  • 6/10/7 décrit le troisième sommet du triangle (doh) ;
  • pour le premier sommet, 8 indique quel sommet utiliser. Dans ce cas, ce sera donc -1.0000000 1.0000000 -1.0000000 (les indices commencent à partir de 1 et non pas de 0 comme en C++) ;
  • 11 indique quelle coordonnée de texture utiliser. Dans ce cas, ce sera donc 0.748355 0.998230
  • 7 indique quelle normale utiliser. Dans ce cas, ce sera donc 0.0000000 1.0000000 -0.0000000.

Ces nombres sont appelés des indices. Ils sont pratiques, car si plusieurs sommets partagent la même position, vous n'avez qu'à écrire une seule fois « v » dans le fichier et l'utiliser plusieurs fois. Cela économise de la mémoire.

La mauvaise nouvelle est que l'on ne peut pas indiquer à OpenGL d'utiliser un indice pour la position, un autre pour la texture et un dernier pour la normale. Donc l'approche que j'ai choisie est de faire un modèle classique, non indexé et de gérer l'indexage plus tard, dans le neuvième tutoriel qui expliquera comment contourner cela.

II-B. Créer un fichier OBJ avec Blender

Comme notre chargeur va être très limité, on doit être très précautionneux et définir les bonnes options lors de l'exportation du fichier. Voici comment cela devrait être dans Blender :

Exportation fichier OBJ avec Blender

II-C. Lecture du fichier

Ok, voilà le code. On doit définir des variables temporaires dans lesquelles on stocke le contenu du .obj :

Sélectionnez
std::vector< unsigned int > vertexIndices, uvIndices, normalIndices;
std::vector< glm::vec3 > temp_vertices;
std::vector< glm::vec2 > temp_uvs;
std::vector< glm::vec3 > temp_normals;

Depuis le cinquième tutoriel : « un cube coloré », vous savez comment ouvrir un fichier :

Sélectionnez
FILE * file = fopen(path, "r");
if( file == NULL ){
    printf("Impossible to open the file !\n");
    return false;
}

On le lit jusqu'à la fin :

Sélectionnez
while( 1 ){

    char lineHeader[128];
    // lit le premier mot de la ligne
    int res = fscanf(file, "%s", lineHeader);
    if (res == EOF)
        break; // EOF = End Of File (fin de fichier). Quitte la boucle.

    // else : analyse lineHeader

On fait l'hypothèse que le premier mot d'une ligne ne fait pas plus de 128 caractères, ce qui est une hypothèse très idiote. Mais c'est un chargeur pour s'amuser, donc c'est ok.

Premièrement, on traite les sommets :

Sélectionnez
if ( strcmp( lineHeader, "v" ) == 0 ){
    glm::vec3 vertex;
    fscanf(file, "%f %f %f\n", &vertex.x, &vertex.y, &vertex.z );
    temp_vertices.push_back(vertex);

Par exemple, si le premier mot de la ligne est 'v', alors le reste doit être trois nombres flottants, donc on crée un glm::vec3 à partir de ceux-ci et on l'ajoute au vecteur.

Sélectionnez
}else if ( strcmp( lineHeader, "vt" ) == 0 ){
    glm::vec2 uv;
    fscanf(file, "%f %f\n", &uv.x, &uv.y );
    temp_uvs.push_back(uv);

Par exemple, si ce n'est pas 'v' mais un 'vt', alors le reste doit être deux flottants, donc on crée un glm::vec2 et on l'ajoute au vecteur.

Même chose pour les normales :

Sélectionnez
}else if ( strcmp( lineHeader, "vn" ) == 0 ){
    glm::vec3 normal;
    fscanf(file, "%f %f %f\n", &normal.x, &normal.y, &normal.z );
    temp_normals.push_back(normal);

Et maintenant le 'f', qui est plus difficile :

Sélectionnez
}else if ( strcmp( lineHeader, "f" ) == 0 ){
    std::string vertex1, vertex2, vertex3;
    unsigned int vertexIndex[3], uvIndex[3], normalIndex[3];
    int matches = fscanf(file, "%d/%d/%d %d/%d/%d %d/%d/%d\n", &vertexIndex[0], &uvIndex[0], &normalIndex[0], &vertexIndex[1], &uvIndex[1], &normalIndex[1], &vertexIndex[2], &uvIndex[2], &normalIndex[2] );
    if (matches != 9){
        printf("File can't be read by our simple parser : ( Try exporting with other options\n");
        return false;
    }
    vertexIndices.push_back(vertexIndex[0]);
    vertexIndices.push_back(vertexIndex[1]);
    vertexIndices.push_back(vertexIndex[2]);
    uvIndices    .push_back(uvIndex[0]);
    uvIndices    .push_back(uvIndex[1]);
    uvIndices    .push_back(uvIndex[2]);
    normalIndices.push_back(normalIndex[0]);
    normalIndices.push_back(normalIndex[1]);
    normalIndices.push_back(normalIndex[2]);

Le code est en réalité très similaire au précédent, sauf qu'il y a plus de données à lire.

II-D. Traiter les données

Donc, ce que l'on a fait était de simplement changer la « forme » des données. On avait une chaîne de caractères, on a maintenant un ensemble de std::vector. Mais ce n'est pas suffisant, nous avons à mettre ceux-ci dans une forme que OpenGL aime. Plus précisément en retirant les indices et en ayant que des glm::vec3 à la place. Cette opération est appelée indexation.

On parcourt chaque sommet (chaque v/vt/vn) pour chaque triangle (chaque ligne avec un « f ») :

Sélectionnez
    // Pour chaque sommet de chaque triangle
    for( unsigned int i=0; i<vertexIndices.size(); i++ ){

L'indice de la position du sommet est vertexIndices[i] :

Sélectionnez
unsigned int vertexIndex = vertexIndices[i];

Donc la position est temp_vertices[ vertexIndex-1 ] (il y a un -1 car le C++ commence à compter à partir de 0 alors que l'indice du fichier OBJ débute à 1, vous vous rappelez ?) :

Sélectionnez
glm::vec3 vertex = temp_vertices[ vertexIndex-1 ];

Et cela donne la position du nouveau sommet :

Sélectionnez
out_vertices.push_back(vertex);

La même chose est appliquée pour les UV et les normales et c'est fini !

III. Utiliser les données chargées

Une fois que l'on a cela, il n'y a presque plus rien à faire. À la place de déclarer l'habituel static const GLfloat g_vertex_buffer_data[] = {…}, vous déclarez un std::vector de sommets (même chose pour les UV et les normales). Vous appelez la fonction loadOBJ avec les bons paramètres :

Sélectionnez
// Lit le fichier .obj
std::vector< glm::vec3 > vertices;
std::vector< glm::vec2 > uvs;
std::vector< glm::vec3 > normals; // Ne sera pas utiliser pour le moment.
bool res = loadOBJ("cube.obj", vertices, uvs, normals);

Et envoyez vos vecteurs à OpenGL à la place de vos tableaux :

Sélectionnez
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(glm::vec3), &vertices[0], GL_STATIC_DRAW);

IV. Résultat

Désolé pour la texture naze, je NE suis PAS un bon artiste Image non disponible. Toute contribution est bienvenue !

Un cube chargé à partir d'un fichier OBJ

V. Autres formats/chargeurs

Ce petit chargeur devrait vous suffire pour démarrer, mais ne sera d'aucune utilité dans la vraie vie. Regardez la page des outils et liens utiles pour une sélection d'outils que vous pouvez utiliser. Notez, par contre, qu'il est préférable d'attendre le neuvième tutoriel avant d'essayer d'utiliser l'un d'entre eux.


OpenGL Moderne - tutoriel 8 : shaders de base

Vu dans ... https://opengl.developpez.com/tutoriels/opengl-tutorial/8-shaders-de-base/

shaders de base

I. Introduction

Dans ce huitième tutoriel, on va voir comment créer quelques shaders de base. Cela inclut :

  • obtenir un effet plus brillant lorsque l'on est plus proche de la source de lumière ;
  • obtenir une surbrillance lorsque l'on regarde le reflet d'une lumière (lumière spéculaire) ;
  • obtenir un effet plus sombre lorsque la lumière n'est pas directement devant le modèle (lumière diffuse) ;
  • énormément tricher (lumière ambiante).

Cela n'inclut pas :

  • les ombres : c'est un immense sujet nécessitant son(ses) propre(s) tutoriel(s) ;
  • les reflets similaires au miroir (incluant les reflets d'eau) ;
  • n'importe quelle interaction sophistiquée avec la matière lumineuse comme le « subsurface scattering » (comme la cire) ;
  • les matériaux anisotropes (comme le métal brossé) ;
  • les shaders basés sur la physique, qui essaient d'imiter au mieux la réalité ;
  • l'occlusion ambiante (il fait plus sombre dans une cave) ;
  • le mélange des couleurs (un tapis rouge fera qu'un plafond blanc sera un peu rouge) ;
  • la transparence ;
  • tout genre d'illumination globale quelle qu'elle soit (c'est le nom regroupant toutes les techniques précédentes).

En un mot : simple.

II. Les normales

Au cours des tutoriels précédents vous avez utilisé des normales sans vraiment savoir ce que c'est.

II-A. Les normales de triangle

La normale d'un plan est un vecteur de longueur 1 perpendiculaire à ce plan.

La normale d'un triangle est un vecteur de longueur 1 qui est perpendiculaire à ce triangle. Il est facilement calculé en utilisant le produit vectoriel de deux de ses côtés (le produit vectoriel de a et b produit un vecteur qui est perpendiculaire aux deux vecteurs a et b, vous vous souvenez ?), normalisé : sa longueur est ramenée à 1. En pseudo code :

Sélectionnez
triangle ( v1, v2, v3 )
côté1 = v2-v1
côté2 = v3-v1
triangle.normale = cross(côté1, côté2).normalize()

Ne mélangez par la variable normale et normalize(). normalize() divise un vecteur (n'importe quel vecteur et pas seulement une normale) par sa longueur afin que sa nouvelle longueur soit de 1. normale n'est qu'un nom pour quelques vecteurs qui représentent, eh bien, la normale.

II-B. Les normales de sommet

Par extension, on appelle la normale d'un sommet la combinaison des normales des triangles alentour. Cela est pratique car dans un vertex shader, on gère des sommets, donc c'est mieux d'avoir l'information sur le sommet. Et en aucun cas, on ne peut avoir d'informations sur les triangles en OpenGL. En pseudo code :

Sélectionnez
sommet v1, v2, v3, ....
triangle tr1, tr2, tr3 // partagent tous le sommet v1
v1.normale = normalize( tr1.normale + tr2.normale + tr3.normale )

II-C. Utiliser les normales de sommet en OpenGL

Pour utiliser les normales en OpenGL, c'est très simple. Une normale est un attribut de sommet, tout comme sa position, sa couleur, ses coordonnées UV… donc, appliquez le travail habituel. La fonction loadOBJ du septième tutoriel lit déjà les normales à partir du fichier OBJ.

Sélectionnez
GLuint normalbuffer;
glGenBuffers(1, &normalbuffer);
glBindBuffer(GL_ARRAY_BUFFER, normalbuffer);
glBufferData(GL_ARRAY_BUFFER, normals.size() * sizeof(glm::vec3), &normals[0], GL_STATIC_DRAW);

et

Sélectionnez
// troisième tampon d'attribut : les normales
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, normalbuffer);
glVertexAttribPointer(
    2,                                // attribut
    3,                                // taille
    GL_FLOAT,                         // type
    GL_FALSE,                         // normalisé ?
    0,                                // nombre d'octets séparant deux sommets dans le tampon
    (void*)0                          // décalage du tableau de tampons
);

et c'est suffisant pour démarrer.

III. La partie diffuse

III-A. L'importance de la normale de la surface

Lorsque la lumière touche un objet, une importante partie de celle-ci est reflétée dans toutes les directions. C'est la composante « diffuse ». (On verra bientôt ce qui se passe avec l'autre partie.)

Schéma de la lumière diffuse

Lorsqu'un certain flux de lumière arrive sur la surface, la surface est illuminée différemment selon l'angle avec lequel la lumière arrive.

Si la lumière est perpendiculaire à la surface, elle est concentrée sur une petite surface. Si elle arrive de biais, la même quantité de lumière s'étale sur une plus grande surface :

Schéma de diffusion de la lumière sur une surface

Cela signifie que chaque point de la surface sera plus sombre avec une lumière de biais (mais rappelez-vous, plus nombreux sont les points étant illuminés avec la même quantité de lumière).

Cela signifie que lorsque vous calculez la couleur d'un pixel, l'angle entre le rayon de lumière et la normale de la surface entre en jeu. Donc, on obtient :

Sélectionnez
// Le cosinus de l'angle entre la normale et le rayon de lumière est
// toujours supérieur à 0
//  - la lumière est verticale par rapport au triangle  -> 1
//  - la lumière est perpendiculaire par rapport au triangle -> 0
float cosTheta = dot( n,l );

color = LightColor * cosTheta;

Dans ce code, n est la normale de la surface et l est le vecteur unitaire qui va de la surface vers la lumière (et non le contraire, même si ce n'est pas intuitif, cela rend les mathématiques plus simples).

III-B. Attention au signe

Quelque chose manque dans la formule du cosTheta. Si la lumière est derrière le triangle, n et l seront opposés, donc n.l sera négatif. Cela signifie que la couleur aura une valeur négative, ce qui ne veut rien dire. Donc nous devons limiter cosTheta à 0 :

Sélectionnez
// Le cosinus de l'angle entre la normale et le rayon de lumière est
// toujours supérieur à 0
//  - la lumière est verticale par rapport au triangle  -> 1
//  - la lumière est perpendiculaire au triangle -> 0
//  - la lumière est derrière le triangle -> 0
float cosTheta = clamp( dot( n,l ), 0,1 );

color = LightColor * cosTheta;

III-C. Couleur de matériel

Bien sûr, la couleur de sortie dépend aussi de la couleur du matériel. Dans cette image, la lumière blanche est composée de lumière verte, rouge et bleue. Lors de la collision avec le matériel rouge, les lumières verte et bleue sont absorbées et seule la lumière rouge reste.

Schéma de diffusion sur matériel rouge

On peut modéliser cela par une simple multiplication :

Sélectionnez
color = MaterialDiffuseColor * LightColor * cosTheta;

III-D. Modéliser la lampe

Premièrement, on fera l'hypothèse que l'on a une lumière ponctuelle qui émet dans toutes les directions de l'espace, comme une bougie.

Avec une telle lumière, le flux lumineux que recevra la surface dépendra de sa distance avec la source de lumière : plus loin elle est, moins elle est illuminée. En fait, la lumière diminuera avec le carré de la distance :

Sélectionnez
color = MaterialDiffuseColor * LightColor * cosTheta / (distance*distance);

Enfin, on a besoin d'un autre paramètre pour contrôler la puissance de la lumière. Cela peut être ajouté à LightColor (et on le fera dans un prochain tutoriel), mais pour le moment utilisez deux variables : la couleur (par exemple, blanche) et la puissance (par exemple 60 watts).

Sélectionnez
color = MaterialDiffuseColor * LightColor * LightPower * cosTheta / (distance*distance);

III-E. Mettre tout ensemble

Pour que ce code fonctionne, on a besoin de plusieurs paramètres (les différentes couleurs et puissances) et d'un peu plus de code.

MaterialDiffuseColor est simplement récupéré à partir de la texture.

LightColor et LightPower sont définies dans le shader avec les variables uniformes GLSL.

CosTheta dépend de n et l. On peut les exprimer dans n'importe quel espace de coordonnées tant qu'il est le même pour les deux. On choisit l'espace caméra car c'est facile de calculer la position de la lumière dans cet espace :

Sélectionnez
// Normale du fragment calculé, dans l'espace caméra
vec3 n = normalize( Normal_cameraspace );
// Direction de la lumière (du fragment vers la lumière)
vec3 l = normalize( LightDirection_cameraspace );

avec les variables Normal_cameraspace et LightDirection_cameraspace calculées dans le vertex shader et passées au fragment shader :

Sélectionnez
// Position finale du sommet, dans l'espace de découpe : MVP * position
gl_Position =  MVP * vec4(vertexPosition_modelspace,1);

// Position du sommet, dans l'espace monde : M * position
Position_worldspace = (M * vec4(vertexPosition_modelspace,1)).xyz;

// Vecteur allant du sommet vers la caméra, dans l'espace caméra.
// Dans l'espace caméra, la caméra est à l'origine (0,0,0).
vec3 vertexPosition_cameraspace = ( V * M * vec4(vertexPosition_modelspace,1)).xyz;
EyeDirection_cameraspace = vec3(0,0,0) - vertexPosition_cameraspace;

// Vecteur allant du sommet vers la lumière, dans l'espace caméra. M est omise car c'est une matrice d'identité.
vec3 LightPosition_cameraspace = ( V * vec4(LightPosition_worldspace,1)).xyz;
LightDirection_cameraspace = LightPosition_cameraspace + EyeDirection_cameraspace;

// Normale du sommet, dans l'espace caméra
Normal_cameraspace = ( V * M * vec4(vertexNormal_modelspace,0)).xyz; // correct seulement si ModelMatrix ne redimensionne pas le modèle ! Utilisez sa transposée inverse sinon.

Ce code peut sembler impressionnant mais il n'y a rien que l'on n'ait pas vu dans le troisième tutoriel : les matrices. J'ai fait attention d'écrire le nom de chaque espace dans le nom des vecteurs pour qu'il soit plus simple de garder trace de ce qui se passe. Vous devriez aussi faire ça.

M et V sont les matrices de Modèle et de Vue, qui sont passées aux shaders de la même façon que MVP.

III-F. Il est temps de travailler

Vous avez tout ce qu'il faut pour coder la lumière diffuse. Allez-y et apprenez à la dure. Image non disponible

III-G. Résultat

Avec seulement la composante diffuse, on obtient le résultat suivant (désolé pour la texture moche encore une fois) :

Suzanne et la lumière diffuse

C'est mieux qu'avant, mais c'est encore bien incomplet. En particulier, l'arrière de Suzanne est complètement noir car nous avons utilisé clamp().

IV. La composante ambiante

La composante ambiante est la plus grande triche existante.

On s'attend à ce que le dos de Suzanne reçoive plus de lumière car, dans la vraie vie, la lampe éclairerait le mur derrière, ce qui éclairerait (légèrement moins) l'arrière de l'objet.

Ceci est extrêmement coûteux à calculer.

Donc, l'astuce habituelle est de simplement imiter cette lumière. En fait, le modèle 3D va émettre de la lumière afin de ne pas apparaître complètement noir.

Cela peut être fait de cette façon :

Sélectionnez
vec3 MaterialAmbientColor = vec3(0.1,0.1,0.1) * MaterialDiffuseColor;
Sélectionnez
color =
 // Ambiant : simule la lumière indirecte
 MaterialAmbientColor +
 // Diffuse : "couleur" de l'objet
 MaterialDiffuseColor * LightColor * LightPower * cosTheta / (distance*distance) ;

Voyez ce que cela donne.

IV-A. Résultat

Ok, donc c'est un petit peu mieux. Vous pouvez ajuster le (0.1, 0.1, 0.1) si vous souhaitez un meilleur résultat.

Suzanne et la lumière ambiante

V. La composante spéculaire

L'autre partie de la lumière qui est réfléchie l'est principalement dans la direction qui est le reflet de la source de lumière sur la surface. C'est la composante spéculaire.

Schéma de la composante spéculaire

Comme vous pouvez le voir dans cette image, cela forme une sorte de lobe. Dans les cas extrêmes, la composante diffuse peut être nulle, le lobe peut être très très étroit (toute la lumière est réfléchie dans une seule direction) et vous obtenez un miroir.

(on peut évidemment ajuster les paramètres pour obtenir un miroir mais, dans notre cas, la seule chose que l'on prend en compte dans ce miroir est la lampe. Donc, cela ferait un miroir très étrange.)

Sélectionnez
// Vecteur de l'œil (vers la caméra)
vec3 E = normalize(EyeDirection_cameraspace);
// Direction dans laquelle le triangle reflète la lumière
vec3 R = reflect(-l,n);
// Cosinus de l'angle entre le vecteur œil et le vecteur de reflexion
// limité à 0
//  - Looking into the reflection -> 1
//  - Looking elsewhere -> < 1
float cosAlpha = clamp( dot( E,R ), 0,1 );

color =
    // Ambiante : simule l'éclairage indirect
    MaterialAmbientColor +
    // Diffuse : "couleur" de l'objet
    MaterialDiffuseColor * LightColor * LightPower * cosTheta / (distance*distance) ;
    // Speculaire : surbrillance réflective, comme un miroir
    MaterialSpecularColor * LightColor * LightPower * pow(cosAlpha,5) / (distance*distance);

R est la direction vers laquelle la lumière reflète. E la direction inverse de la vue (tout comme nous l'avons fait pour l) ; si l'angle entre ces deux vecteurs est petit, cela signifie que l'on regarde directement dans le reflet.

pow(cosAlpha,5) est utilisé pour contrôler la largeur du lobe de lumière spéculaire. Augmentez de 5 pour obtenir un lobe plus mince.

V-A. Résultat final

Suzanne avec une lumière spéculaire, diffuse et ambiante

Remarquez la surbrillance spéculaire sur le nez et les sourcils.

Ce modèle de shaders a été utilisé pendant des années de par sa simplicité. Il possède un grand nombre de problèmes, donc il est remplacé par des modèles basés sur la physique comme le BRDF à microfacettes, mais on verra cela plus tard.

Dans le prochain tutoriel, on apprendra à améliorer les performances de votre VBO. Cela sera le premier tutoriel intermédiaire !


OpenGL Moderne - tutoriel 9 : indexation de VBO

Vu dans ... https://opengl.developpez.com/tutoriels/opengl-tutorial/9-indexation-VBO/

indexation de VBO

I. Le principe de l'indexation

Jusqu'à présent, lors de la construction du VBO, on avait dupliqué les sommets à chaque fois que deux triangles partageaient un côté.

Dans ce tutoriel, on va s'initier à l'indexation, qui permet de réutiliser le même sommet encore et encore. Cela est possible grâce au tampon d'indices.

Exemple des bénéfices de l'indexation

Le tampon d'indices contient des entiers, trois pour chaque triangle dans le modèle, faisant référence aux différents tampons d'attributs (position, couleur, coordonnées UV, autres coordonnées UV, normales…). C'est un peu comme dans le fichier .OBJ, avec une grande différence : il n'y a qu'un SEUL tampon d'indices. Cela signifie que pour qu'un sommet soit partagé entre deux triangles, tous les attributs doivent être les mêmes.

II. Partagé contre séparé

On va prendre en exemple les normales. Dans ce schéma, l'artiste qui a créé ces deux triangles voulait certainement qu'ils représentent une surface lisse. On peut donc fusionner les normales des deux triangles en une normale d'un seul sommet. Pour des soucis de visualisation, j'ai ajouté une ligne rouge qui représente l'aspect de la surface lisse.

Partage de normales entre deux triangles

Dans le second schéma, par contre, l'artiste a visiblement voulu une « couture », un côté pointu. Mais si on fusionne les normales, cela signifie que le shader va effectuer un adoucissement, comme d'habitude, et créer un aspect plus doux, comme avant :

Schéma où la fusion de normales ne fonctionne pas

Donc, dans ce cas il est préférable d'avoir deux normales distinctes, une pour chaque sommet. La seule façon de faire cela en OpenGL est de dupliquer entièrement le sommet, avec son ensemble d'attributs :

Deux sommets pour un

III. VBO indexé en OpenGL

Il est très simple d'utiliser l'indexation. Premièrement, vous devez créer un tampon supplémentaire, que vous allez remplir avec les bons indices. Le code est le même qu'avant, mais maintenant c'est un ELEMENT_ARRAY_BUFFER, et non un ARRAY_BUFFER.

Sélectionnez
std::vector<unsigned int> indices;

// Remplissez "indices" comme il faut

// Génère un tampon pour les indices
GLuint elementbuffer;
glGenBuffers(1, &elementbuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), &indices[0], GL_STATIC_DRAW);

Et pour dessiner le modèle, remplacez simplement glDrawArrays par :

Sélectionnez
// Index buffer
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer);

// Dessine les triangles
glDrawElements(
    GL_TRIANGLES,      // mode
    indices.size(),    // nombre
    GL_UNSIGNED_INT,   // type
    (void*)0           // décalage  du tableau de tampons
);

Il est préférable d'utiliser des « unsigned short » que des « unsigned int », car cela utilise moins de mémoire et accélère le programme.

IV. Remplir le tampon d'indices

On a maintenant un problème. Comme je l'ai dit avant, OpenGL ne peut utiliser qu'un seul tampon d'indices, alors que le format OBJ (ainsi que d'autres formats de fichier 3D populaires comme Collada) utilise un tampon d'indices par attribut. Cela signifie que d'une façon ou d'une autre, on doit convertir N tampons d'indices en un.

L'algorithme pour ce faire est le suivant :

Sélectionnez
Pour chaque sommet en entrée
    Essayer de trouver un sommet similaire ( = identique pour tous les attributs) parmi ceux déjà en sortie
    Si trouvé :
        Un sommet similaire est déjà dans le VBO, utilisez le !
    Si non trouvé :
        Aucun sommet similaire trouvé, ajouter le au VBO

Le code C++ peut être trouvé dans le fichier common/vboindexer.cpp. Il est intensément commenté, donc si vous comprenez l'algorithme ci-dessus, cela devrait aller.

Le critère pour la similarité des sommets est que la position, la normale et les coordonnées UV doivent êtres identiques. Vous devez l'adapter si vous ajoutez plus d'attributs.

La recherche d'un sommet similaire est effectuée d'une manière linéaire pour garder l'algorithme simple. Une std::map serait plus appropriée.

V. Extra : le compteur de FPS

Ce n'est pas directement lié à l'indexation, mais c'est une bonne occasion de jeter un œil au compteur de FPS car on peut possiblement voir une amélioration de la vitesse grâce à l'indexation. D'autres outils pour les performances sont disponibles dans la page Outils - débogueurs.


OpenGL Moderne - tutoriel 10 : la transparence

Vu dans ... https://opengl.developpez.com/tutoriels/opengl-tutorial/10-la-transparence/

la transparence

I. La canal alpha

Le concept du canal alpha est très simple. À la place d'écrire un résultat en RGB, vous écrivez un résultat en RGBA :

Sélectionnez
// Données de sorties : c'est maintenant un vec4
out vec4 color;

les trois premières composantes sont toujours accessibles avec .xyz, tandis que la dernière est accessible avec .a :

Sélectionnez
color.a = 0.3;

Contre toute logique, alpha = opacité, donc alpha = 1 signifie complètement opaque alors que alpha = 0 signifie complètement transparent.

Ici, on a simplement codé en dur le canal alpha à 0.3, mais vous souhaitez sûrement utiliser une variable uniforme ou la lire à partir d'une texture RGBA (le format TGA supporte le canal alpha et GLFW supporte le format TGA).

Voici le résultat. Assurez-vous de désactiver le « backface culling » (suppression des faces arrières) (glDisable(GL_CULL_FACE)) car comme on peut voir à travers le modèle, on pourrait penser qu'il n'a pas de face « arrière ».

II. L'ordre est important

La capture d'écran précédente paraît correcte, mais c'est uniquement car on est chanceux.

II-A. Le problème

Ici, j'ai dessiné deux carrés ayant 50 % d'alpha, un vert et un rouge. Vous pouvez voir que l'ordre est important, la couleur finale donne une importante piste visuelle sur la perception de la profondeur.

Ordre de transparence

Ce phénomène se produit aussi sur la scène. Changez le point de vue :

Problème d'ordre d'affichage et de transparence

En fait, c'est un problème très complexe. Vous ne voyez jamais énormément de transparence dans les jeux vidéo, n'est-ce pas ?

II-B. Solution classique

La solution classique est de trier tous les triangles transparents. Oui, TOUS les triangles transparents.

  • Dessinez la partie du monde opaque afin que le tampon de profondeur puisse déjà rejeter les triangles transparents cachés.
  • Triez les triangles transparents, du plus loin aux plus proches.
  • Dessinez les triangles transparents.

Vous pouvez trier ce que vous voulez avec qsort (en C) ou std::sort (en C++). Je n'entrerai pas dans les détails, car…

II-C. Mise en garde

Faire comme cela fonctionne (plus de détails dans la section suivante), mais :

  • vous allez être limité par la bande passante. En effet, chaque fragment sera écrit 10, 20 fois ou même plus. Cela est beaucoup trop pour le pauvre bus mémoire. Habituellement, le tampon de profondeur permet de rejeter assez de fragments « lointains », mais là, vous les triez explicitement, faisant que le tampon de profondeur soit totalement inutile ;
  • vous allez faire cela quatre fois par pixel (on utilise le 4xMSAA), sauf si vous utilisez une optimisation plus intelligente ;
  • le tri des triangles prend du temps ;
  • si vous devez changer de texture, ou pire, de shader, de triangle en triangle, vous allez avoir de sérieux problèmes de performance. Ne le faites pas.

Une solution assez bonne est souvent de :

  • limitez à un maximum le nombre de polygones transparents ;
  • utilisez le même shader et la même texture pour tous les polygones transparents ;
  • s'ils sont sensés être très différents, utilisez votre texture ;
  • si vous pouvez éviter le tri et que cela n'est pas trop moche, vous pouvez vous dire chanceux.

II-D. Transparence indépendante de l'ordre

De nombreuses autres techniques sont intéressantes si votre moteur a vraiment, vraiment besoin de l'état de l'art de la transparence :

  • le papier original 2001 Depth Peeling : résultats parfait au niveau du pixel, pas très rapide ;
  • Dual Depth Peeling : une petite amélioration ;
  • de nombreux papiers sur le tri comptage (bucket sort) utilisent un tableau de fragments ; les tris par la profondeur dans le shader ;
  • démonstration d'ATI Mecha : bon et rapide, mais compliqué à implémenter et nécessite du matériel récent. Utilise une liste chaînées de fragments ;
  • variation de la technique d'ATI par Cyril Crassin : implémentation encore plus compliquée.

Même un jeu récent comme Little Big Planet, s'exécutant sur une console puissante, utilise une unique couche de transparence.

III. La fonction de mélange

Afin que le code précédent fonctionne, vous devez initialiser la fonction de mélange :

Sélectionnez
// Active le mélange
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

Ce qui signifie :

Sélectionnez
nouvelle couleur dans le tampon d'image =
    alpha actuel dans le tampon d'image * couleur actuelle dans le tampon d'image +
    (1 - alpha actuel dans le tampon d'image) * la couleur de sortie du shader

Exemple de l'image ci-dessus, avec le rouge au-dessus :

Sélectionnez
nouvelle couleur = 0.5 * (0,1,0) 1+ (1-0.5) * (1,0.5,0.5) ; // (le rouge était déjà mélangé avec le fond blanc) ;
nouvelle couleur = (1, 0.75, 0.25) = le même orange


OpenGL Moderne - tutoriel 11 : texte 2D

Vu dans ... https://opengl.developpez.com/tutoriels/opengl-tutorial/11-texte-2D/

I. Introduction

Dans ce tutoriel, on va apprendre à afficher un texte en 2D par-dessus le contenu 3D. Ici, ce sera un simple chronomètre :

Un chronomètre au-dessus de Suzanne

II. Les fonctions

On va implémenter cette simple interface (dans common/text2D.h) :

Sélectionnez
void initText2D(const char * texturePath);
void printText2D(const char * text, int x, int y, int size);
void cleanupText2D();

Afin que le code fonctionne, que ce soit en 640 x 480 ou 1080p, x et y seront des coordonnées dans l'espace [0-800][0-600]. Le vertex shader adaptera les coordonnées pour correspondre à la taille de l'écran.

Regardez common/text2D.cpp pour voir l'implémentation complète.

III. La texture

initText2D lit simplement une texture et un couple de shaders. Il n'y a rien de spécial là-dedans, mais voyez à quoi ressemble la texture :

Texture de police

Cette texture a été générée avec CBFG, un des nombreux outils pouvant générer une texture à partir d'une police de caractères. Puis la texture a été importée dans Paint.NET où j'ai ajouté un fond rouge (seulement pour des raisons de visualisation : là où c'est rouge, cela devrait être transparent).

Le but de printText2D sera donc de générer des rectangles avec les positions écran appropriées et les coordonnées de texture.

IV. L'affichage

On doit remplir ces tampons :

Sélectionnez
std::vector<glm::vec2> vertices;
std::vector<glm::vec2> UVs;

Pour chaque caractère, on calcule les coordonnées des quatre sommets définissant le rectangle et on ajoute les deux triangles :

Sélectionnez
for ( unsigned int i=0 ; i<length ; i++ ){

    glm::vec2 vertex_up_left    = glm::vec2( x+i*size     , y+size );
    glm::vec2 vertex_up_right   = glm::vec2( x+i*size+size, y+size );
    glm::vec2 vertex_down_right = glm::vec2( x+i*size+size, y      );
    glm::vec2 vertex_down_left  = glm::vec2( x+i*size     , y      );

    vertices.push_back(vertex_up_left   );
    vertices.push_back(vertex_down_left );
    vertices.push_back(vertex_up_right  );

    vertices.push_back(vertex_down_right);
    vertices.push_back(vertex_up_right);
    vertices.push_back(vertex_down_left);

Maintenant les coordonnées UV. La coordonnée du coin supérieur gauche est calculée comme suit :

Sélectionnez
char character = text[i];
float uv_x = (character%16)/16.0f;
float uv_y = (character/16)/16.0f;

Cela fonctionne (en quelque sorte - voyez ci-dessus) car le code ASCII pour A est 65.

65 % 16 = 1, donc A est sur la colonne numéro 1 (on commence à 0 !).

65 / 16 = 4, donc A est sur la ligne numéro 4 (c'est une division entière, donc ce n'est pas 4.0625 comme cela devrait l'être).

Les deux sont divisés par 16.0 pour rentrer dans l'échelle [0.0 - 1.0] nécessaire aux textures OpenGL.

Et maintenant, on doit faire une chose très proche de ce que nous faisions, mais pour les coordonnées de texture :

Sélectionnez
   glm::vec2 uv_up_left    = glm::vec2( uv_x           , 1.0f - uv_y );
   glm::vec2 uv_up_right   = glm::vec2( uv_x+1.0f/16.0f, 1.0f - uv_y );
   glm::vec2 uv_down_right = glm::vec2( uv_x+1.0f/16.0f, 1.0f - (uv_y + 1.0f/16.0f) );
   glm::vec2 uv_down_left  = glm::vec2( uv_x           , 1.0f - (uv_y + 1.0f/16.0f) );

   UVs.push_back(uv_up_left   );
   UVs.push_back(uv_down_left );
   UVs.push_back(uv_up_right  );

   UVs.push_back(uv_down_right);
   UVs.push_back(uv_up_right);
   UVs.push_back(uv_down_left);
}

Le reste est habituel : liaison des tampons, remplissage, sélection du shader program, liaison de la texture, activation/liaison/configuration des attributs de sommets, activation du mélange et appel de la fonction glDrawArrays. Hourra ! Vous avez fini.

Les coordonnées sont générées sur l'échelle [0-800][0-600]. En d'autres mots, il n'y a PAS BESOIN de matrices ici. Le vertex shader doit juste les passer à l'échelle [-1,1][-1,1] avec une simple opération mathématique (cela aurait aussi pu être fait en C++).

Sélectionnez
void main(){

    // position de sortie du sommet, dans l'espace de découpage
    // map [0..800][0..600] to [-1..1][-1..1]
    vec2 vertexPosition_homoneneousspace = vertexPosition_screenspace - vec2(400,300); // [0..800][0..600] -> [-400..400][-300..300]
    vertexPosition_homoneneousspace /= vec2(400,300);
    gl_Position =  vec4(vertexPosition_homoneneousspace,0,1);

    // coordonnées UV du sommet. Pas d'espace de coordonnées spécifique pour celles-ci.
    UV = vertexUV;
}

Le fragment shader ne fait que très peu de choses :

Sélectionnez
void main(){
    color = texture( myTextureSampler, UV );
}

D'ailleurs, n'utilisez pas ce code en production, car il ne gère que l'alphabet latin. Ou alors ne vendez rien en Inde, Chine, Japon (ou même Allemagne, car il n'y a pas de ß dans cette image). Cette texture marchera principalement en France (remarquez la présence des é, à, ç, etc.) car elle a été générée avec ma locale. Et soyez attentionné lors de l'adaptation du code des autres tutoriels ou lorsque vous avez recours à des bibliothèques, la plupart d'entre eux utilisent OpenGL 2 qui n'est pas compatible. Malheureusement, je ne connais pas de bibliothèque qui gère UTF-8.

À propos de cela, vous devez lire The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!) (le minimum absolu que chaque développeur de logiciels devrait absolument connaître sur l'Unicode et les ensembles de caractères (pas d'excuse !) de Joel Spolsky.

Regardez aussi l'article de Valve si vous avez besoin de grand texte.


OpenGL Moderne - tutoriel 12 : extensions OpenGL

Vu dans ... https://opengl.developpez.com/tutoriels/opengl-tutorial/12-extensions-OpenGL/

extensions OpenGL

I. Extensions

Avec chaque nouvelle génération, les performances des GPU augmentent, permettant ainsi d'afficher plus de triangles et plus de pixels. Néanmoins, la performance brute n'est pas le seul intérêt. NVIDIA, AMD et Intel améliorent aussi leurs cartes graphiques en y ajoutant des fonctionnalités. En voici quelques exemples.

I-A. ARB_fragment_program

En 2002, les GPU n'avaient pas de vertex shader ni de fragment shader : tout était codé en dur dans la puce. C'était ce que l'on appelait le pipeline à fonctionnalités fixes (Fixed-Function Pipeline (FFP)). La plus récente version de la bibliothèque, qui était alors OpenGL 1.3, ne proposait aucun moyen de créer, manipuler et utiliser les « shaders », car ils n'existaient même pas. Mais NVIDIA a décidé que cela pourrait être pratique de décrire le processus de rendu avec du code, à la place de la centaine d'indicateurs et de variables d'états. C'est ainsi qu'a été créé ARB_fragment_program : il n'y avait pas de GLSL, mais à la place, vous pouviez écrire des trucs comme :

Sélectionnez
!!ARBfp1.0 MOV result.color, fragment.color; END

Évidemment, pour indiquer à OpenGL d'utiliser un tel code, vous aviez besoin de fonctions spéciales, qui n'étaient pas encore dans OpenGL. Avant de plonger dans les explications, un autre exemple.

I-B. ARB_debug_output

Ok, vous vous dites que ce ARB_fragment_program est trop vieux, et que vous n'avez certainement plus besoin de ce truc ? En fait, il y a de nouvelles extensions qui sont très pratiques. L'une d'entre elles est ARB_debug_output, qui expose une fonctionnalité qui n'existait pas dans OpenGL 3.3 mais que vous pouvez/devez tout de même utiliser. Elle définit des symboles comme GL_DEBUG_SYNCHRONOUS_ARB ou GL_DEBUG_SEVERITY_MEDIUM_ARB et des fonctions comme DebugMessageCallbackARB. Le bon point de cette extension est qu'à chaque fois que vous écrivez un code incorrect, comme :

Sélectionnez
glEnable(GL_TEXTURE); // Incorrect ! Vous voulez sûrement utiliser GL_TEXTURE_2D !

vous pouvez avoir une erreur et l'emplacement exact du problème.

Vous avez donc appris que :

  • les extensions sont très utiles, même en OpenGL 3.3 moderne ;
  • il faut utiliser ARB_debug_output ! Voir les liens ci-dessous.
Point d'arrêt dans du code OpenGL

I-C. Récupérer une extension - la méthode difficile

La méthode « manuelle » pour vérifier si une extension est disponible est d'utiliser ce morceau de code (provenant du wiki OpenGL.org) :

Sélectionnez
int NumberOfExtensions;
glGetIntegerv(GL_NUM_EXTENSIONS, &NumberOfExtensions);
for(i=0; i<NumberOfExtensions; i++) {
  const GLubyte *ccc=glGetStringi(GL_EXTENSIONS, i);
  if ( strcmp(ccc, (const GLubyte *)"GL_ARB_debug_output") == 0 ){
    // L'extension est supporté par la machine et le pilote
    // Essaie de récupérer la fonction "glDebugMessageCallbackARB" :
    glDebugMessageCallbackARB  = (PFNGLDEBUGMESSAGECALLBACKARBPROC) wglGetProcAddress("glDebugMessageCallbackARB");
  }
}

I-D. Récupérer une extension - la méthode facile

Somme toute, c'est très compliqué. Les bibliothèques comme GLEW, Glee, gl3w, etc. rendent cela plus facile. Par exemple, avec GLEW, vous n'avez qu'à appeler glewInit() après avoir créé la fenêtre et les variables pratiques comme :

Sélectionnez
if (GLEW_ARB_debug_output){ // Ta-Dah ! }

sont créées.

debug_output est spéciale, car vous devez l'activer à la création du contexte. Dans GLFW, cela est fait avec glfwOpenWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, 1);

I-E. ARB vs EXT vs…

Le nom de chaque extension contient des informations sur sa disponibilité :

GL_ : toutes les plates-formes ;

GLX_ : Linux et Mac seulement (X11) ;

WGL_ : Windows seulement ;

EXT : une extension générique ;

ARB : l'extension a été acceptée par tous les membres du OpenGL Architecture Review Board (les extensions EXT sont souvent promues ARB au bout d'un moment) ;

NV/AMD/INTEL : assez évident =).

II. Concevoir avec les extensions

II-A. Le problème

Disons que votre application OpenGL 3.3 nécessite d'afficher quelques lignes larges. Vous pouvez écrire un vertex shader compliqué pour ce faire, ou simplement vous reposer sur GL_NV_path_rendering, qui gère tous les problèmes pour vous.

Vous aurez donc un code qui ressemble à ceci :

Sélectionnez
if ( GLEW_NV_path_rendering ){
    glPathStringNV( ... ); // Affiche la forme. Facile !
}else{
    // Sinon quoi ? Vous avez toujours besoin d'afficher des lignes
    // sur les machines NVIDIA plus vieilles, sur les machines AMD, sur les machines INTEL !
    // Donc, vous devez tout de même l'implémenter vous-même !
}

II-B. Choisir la limite

Une possibilité est de choisir d'utiliser une extension lorsque le gain dans la qualité du rendu ou la performance dépasse les souffrances de la maintenance de deux codes.

Par exemple, Braid (le jeu 2D où vous voyagez dans le temps) possède plein d'effets de déformation d'images, lorsque vous jouez avec le temps, qui ne sont simplement pas affichés avec du matériel plus ancien.

Avec OpenGL 3.3 et supérieur, vous avez déjà 99 % des outils dont vous allez avoir besoin. Quelques extensions peuvent être utiles, comme GL_AMD_pinned_memory, mais c'est souvent différent d'il y a quelques années, lorsqu'avoir GL_ARB_framebuffer_object (utilisé pour le rendu sur texture), pouvait rendre le jeu dix fois plus beau.

Si vous devez gérer le plus vieux matériel, alors OpenGL 3.3 ne sera pas disponible et vous allez avoir à utiliser OpenGL 2+ à la place. Vous ne pourrez pas supposer que vous avez toutes ces extensions fantaisistes et vous allez devoir vous débrouiller sans.

Pour plus de détails, regardez par exemple la version OpenGL 2.1 du quatorzième tutoriel - rendu sur texture, ligne 152, où je dois vérifier la présence de GL_ARB_framebuffer_object à la main.

III. Conclusion

Les extensions OpenGL fournissent une méthode efficace pour étendre les capacités d'OpenGL, suivant le GPU de l'utilisateur.

Bien que les extensions soient principalement utilisées pour les usages avancés depuis que la plupart des fonctionnalités sont déjà présentes dans le cœur, il reste important de connaître comment elles fonctionnent et comment vous pouvez les utiliser pour améliorer votre logiciel - avec le coût d'une maintenance plus chère.

IV. Pour continuer

  • tutoriel debug_output par Aks, vous pouvez passer la première étape grâce à GLFW ;
  • le registre des extensions OpenGL, toutes les spécifications des extensions. La bible ;
  • GLEW, le OpenGL Extension Wrangler Library ;
  • gl3w, simple chargement de profil cœur OpenGL 3/4.


OpenGL Moderne - tutoriel 13 : application des normales

Vu dans ... https://opengl.developpez.com/tutoriels/opengl-tutorial/13-applications-des-normales/

application des normales

I. Introduction

Depuis le huitème tutoriel : shaders de base, vous savez comment obtenir un ombrage décent en utilisant les normales des triangles. Jusqu'à présent, il existait un inconvénient : on n'avait qu'une seule normale par sommet dans chaque triangle et elle va varier doucement, contrairement aux couleurs, qui sont échantillonnées à partir d'une texture. L'idée de base de l'application des normales (normal mapping) est de donner aux normales des variations similaires.

II. Textures de normales

Une « texture de normales » ressemble à cela :

Exemple texture de normales

Dans chaque texel RGB est encodé un vecteur XYZ : chacune des composantes d'une couleur est comprise entre 0 et 1 et chacune des composantes d'un vecteur est entre -1 et 1, donc la conversion d'un texel en normale s'effectue ainsi :

Sélectionnez
normal = (2*color)-1 // sur chaque composante

La texture a une teinte bleue car après tout, la normale pointe vers « l'extérieur de la surface » ; comme toujours X va vers la droite, Y vers le haut.

Cette texture est appliquée exactement comme la texture de diffusion ; le gros problème est la conversion de notre normale, qui est exprimée dans l'espace de chaque triangle (espace tangent, aussi appelé espace de l'image), vers l'espace modèle (car c'est ce que l'on utilise dans notre équation d'ombrage).

III. Tangente et bitangente

Maintenant, que vous connaissez les matrices, vous savez que pour définir un espace (dans notre cas, l'espace tangent), on a besoin de trois vecteurs. On a déjà le vecteur UP : c'est la normale, donnée par Blender ou calculée à partir du triangle à l'aide d'un produit scalaire. Elle est représentée en bleu, tout comme la teinte de la texture de normales :

Ensuite, on a besoin d'une tangente, T : un vecteur perpendiculaire à la surface. Mais il y a tellement de vecteurs correspondant à ce critère :

Lequel choisir ? En théorie, n'importe lequel, mais on doit être consistant avec ses voisins pour éviter de voir apparaître d'affreuses bordures. La méthode standard est d'orienter la tangente dans la même direction que les coordonnées de textures :

Vecteur tangent à partir des UV

Comme on a besoin de trois vecteurs pour définir une base, on doit aussi calculer la bitangente B (qui peut être n'importe quel autre vecteur tangent, mais si tout est perpendiculaire, les mathématiques sont simplifiées) :

Normale, tangente et bitangente à partir des UV

Voici l'algorithme : si on appelle deltaPos1 et deltaPos2 deux côtés de notre triangle et deltaUV1 et deltaUV2 les différences de coordonnées UV correspondante, on peut exprimer notre problème avec l'équation suivante :

Sélectionnez
deltaPos1 = deltaUV1.x * T + deltaUV1.y * B
deltaPos2 = deltaUV2.x * T + deltaUV2.y * B

Trouvez les solutions de ce système pour T et B et vous obtenez vos vecteurs ! Voir le code ci-dessous.

Une fois que l'on a les vecteurs T, B et N, on obtient aussi cette jolie matrice qui nous permet de passer de l'espace tangent à l'espace modèle :

⎡⎣⎢TxTyTzBxByBzNxNyNz⎤⎦⎥[TxBxNxTyByNyTzBzNz]

Avec cette matrice TBN, on peut transformer les normales (extraites à partir de la texture) dans l'espace modèle. Par contre, habituellement c'est l'inverse que l'on fait : transformer tout de l'espace modèle vers l'espace tangent et garder les normales telles quelles. Tous les calculs sont faits dans l'espace tangent, ce qui ne change absolument rien.

Pour effectuer cette transformation inverse, on doit simplement prendre l'inverse de la matrice, ce qui dans ce cas (une matrice orthogonale, où chaque vecteur est perpendiculaire aux autres. Voir la section « Aller plus loin ».) est aussi une transposée, moins coûteuse à calculer :

Sélectionnez
invTBN = transpose(TBN)

Soit :

⎡⎣⎢TxTyTzBxByBzNxNyNz⎤⎦⎥T=⎡⎣⎢TxBxNxTyByNyTzBzNz⎤⎦⎥[TxBxNxTyByNyTzBzNz]T=[TxTyTzBxByBzNxNyNz]

IV. Préparation du VBO

IV-A. Calcul des tangentes et bitangentes

Comme les tangentes et bitangentes sont nécessaires en plus des normales, on doit les calculer pour la globalité du modèle. On fait cela dans une fonction à part :

Sélectionnez
void computeTangentBasis(
    // entrées
    std::vector<glm::vec3> & vertices,
    std::vector<glm::vec2> & uvs,
    std::vector<glm::vec3> & normals,
    // sorties
    std::vector<glm::vec3> & tangents,
    std::vector<glm::vec3> & bitangents
){

Pour chaque triangle, on calcule le côté (deltaPos) et le deltaUV.

Sélectionnez
for ( int i=0; i<vertices.size(); i+=3){

    // Raccourcis pour les sommets
    glm::vec3 & v0 = vertices[i+0];
    glm::vec3 & v1 = vertices[i+1];
    glm::vec3 & v2 = vertices[i+2];

    // Raccourcis pour les UV
    glm::vec2 & uv0 = uvs[i+0];
    glm::vec2 & uv1 = uvs[i+1];
    glm::vec2 & uv2 = uvs[i+2];

    // Côtés du triangle : delta des positions
    glm::vec3 deltaPos1 = v1-v0;
    glm::vec3 deltaPos2 = v2-v0;

    // delta UV
    glm::vec2 deltaUV1 = uv1-uv0;
    glm::vec2 deltaUV2 = uv2-uv0;

On peut utiliser la formule pour calculer la tangente et la bitangente :

Sélectionnez
float r = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x);
glm::vec3 tangent = (deltaPos1 * deltaUV2.y   - deltaPos2 * deltaUV1.y)*r;
glm::vec3 bitangent = (deltaPos2 * deltaUV1.x   - deltaPos1 * deltaUV2.x)*r;

Finalement, on remplit les tampons tangents et bitangents. Rappelez-vous, ces tampons ne sont pas encore indexés, donc chaque sommet possède sa propre copie.

Sélectionnez
    // Définit la même tangeante pour les trois sommets du triangle.
    // Ils seront rassemblés plus tard, dans vboindexer.cpp
    tangents.push_back(tangent);
    tangents.push_back(tangent);
    tangents.push_back(tangent);

    // Même chose pour les binormales
    bitangents.push_back(bitangent);
    bitangents.push_back(bitangent);
    bitangents.push_back(bitangent);

}

IV-B. Indexation

L'indexation du VBO est très similaire à ce que l'on avait l'habitude de faire, mais il y a une légère différence.

Si on trouve un sommet similaire (même position, même normale, même coordonnées de texture), on ne souhaite pas utiliser la tangente et bitangente ; au contraire, on souhaite en faire la moyenne. Donc, modifiez un peu le vieux code :

Sélectionnez
// Essaie de trouver un sommet similaire dans out_XXXX
unsigned int index;
bool found = getSimilarVertexIndex(in_vertices[i], in_uvs[i], in_normals[i],     out_vertices, out_uvs, out_normals, index);

if ( found ){ // Un sommet similaire est déjà dans le VBO, on l'utilise à la place !
    out_indices.push_back( index );

    // Moyenne des tangentes et bitangentes
    out_tangents[index] += in_tangents[i];
    out_bitangents[index] += in_bitangents[i];
}else{ // Sinon, on doit l'ajouter dans les données de sortie.
    // Faire comme d'habitude
    [...]
}

Notez que l'on ne normalise rien ici. En réalité c'est pratique, car de cette façon, les petits triangles, qui ont une tangente et bitangente plus petites, auront un effet diminué sur le vecteur final par rapport aux grands triangles (qui contribueront plus à la forme finale).

V. Le shader

V-A. Tampons et variables uniformes supplémentaires

On a besoin de deux nouveaux tampons, un pour les tangentes, l'autre pour les bitangentes :

Sélectionnez
GLuint tangentbuffer;
glGenBuffers(1, &tangentbuffer);
glBindBuffer(GL_ARRAY_BUFFER, tangentbuffer);
glBufferData(GL_ARRAY_BUFFER, indexed_tangents.size() * sizeof(glm::vec3), &indexed_tangents[0], GL_STATIC_DRAW);

GLuint bitangentbuffer;
glGenBuffers(1, &bitangentbuffer);
glBindBuffer(GL_ARRAY_BUFFER, bitangentbuffer);
glBufferData(GL_ARRAY_BUFFER, indexed_bitangents.size() * sizeof(glm::vec3), &indexed_bitangents[0], GL_STATIC_DRAW);

On a aussi besoin d'une nouvelle variable uniforme pour notre nouvelle texture de normales :

Sélectionnez
[...]
GLuint NormalTexture = loadTGA_glfw("normal.tga");
[...]
GLuint NormalTextureID  = glGetUniformLocation(programID, "NormalTextureSampler");

Et d'une autre pour la matrice 3x3 de modèle-vue. Cela n'est pas strictement obligatoire, mais c'est plus simple ; plus d'informations là-dessus plus tard. On a besoin que de la partie 3 x 3 supérieure gauche car on va multiplier des directions : on peut donc se débarrasser de la partie liée à la translation.

Sélectionnez
GLuint ModelView3x3MatrixID = glGetUniformLocation(programID, "MV3x3");

Voici ce qu'est devenu le code d'affichage :

Sélectionnez
// Nettoie l'écran
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// Utilise le shader
glUseProgram(programID);

// Calcule la matrice MVP à partir des entrées clavier et souris
computeMatricesFromInputs();
glm::mat4 ProjectionMatrix = getProjectionMatrix();
glm::mat4 ViewMatrix = getViewMatrix();
glm::mat4 ModelMatrix = glm::mat4(1.0);
glm::mat4 ModelViewMatrix = ViewMatrix * ModelMatrix;
glm::mat3 ModelView3x3Matrix = glm::mat3(ModelViewMatrix); // Take the upper-left part of ModelViewMatrix
glm::mat4 MVP = ProjectionMatrix * ViewMatrix * ModelMatrix;

// Envoie les transformations au shader actuellement lié,
// dans la variable uniforme "MVP"
glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]);
glUniformMatrix4fv(ModelMatrixID, 1, GL_FALSE, &ModelMatrix[0][0]);
glUniformMatrix4fv(ViewMatrixID, 1, GL_FALSE, &ViewMatrix[0][0]);
glUniformMatrix4fv(ViewMatrixID, 1, GL_FALSE, &ViewMatrix[0][0]);
glUniformMatrix3fv(ModelView3x3MatrixID, 1, GL_FALSE, &ModelView3x3Matrix[0][0]);

glm::vec3 lightPos = glm::vec3(0,0,4);
glUniform3f(LightID, lightPos.x, lightPos.y, lightPos.z);

// Lie notre texture diffuse à l'unité de texture 0
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, DiffuseTexture);
// Définit notre échantillonneur "DiffuseTextureSampler" à l'unité de texture 0
glUniform1i(DiffuseTextureID, 0);

// Lie notre texture de normales à l'unité de texture 1
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, NormalTexture);
// Définit notre échantilloneur "Normal    TextureSampler" à l'unité de texture 1
glUniform1i(NormalTextureID, 1);

// 1er tampon d'attributs : sommets
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
glVertexAttribPointer(
    0,                  // attribut
    3,                  // taille
    GL_FLOAT,           // type
    GL_FALSE,           // normalisé ?
    0,                  // nombre d'octets séparant deux sommets dans le tampon
    (void*)0            // décalage du tableau de tampon
);

// 2d tampon d'attributs : UVs
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, uvbuffer);
glVertexAttribPointer(
    1,                                // attribut
    2,                                // taille
    GL_FLOAT,                         // type
    GL_FALSE,                         // normalisé ?
    0,                                // nombre d'octets séparant deux sommets dans le tampon
    (void*)0                          // décalage du tableau de tampon
);

// 3e tampon d'attributs : normales
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, normalbuffer);
glVertexAttribPointer(
    2,                                // attribut
    3,                                // taille
    GL_FLOAT,                         // type
    GL_FALSE,                         // normalisé ?
    0,                                // nombre d'octets séparant deux sommets dans le tampon
    (void*)0                          // décalage du tableau de tampon
);

// 4e tampon d'attributs : tangentes
glEnableVertexAttribArray(3);
glBindBuffer(GL_ARRAY_BUFFER, tangentbuffer);
glVertexAttribPointer(
    3,                                // attribut
    3,                                // taille
    GL_FLOAT,                         // type
    GL_FALSE,                         // normalisé ?
    0,                                // nombre d'octets séparant deux sommets dans le tampon
    (void*)0                          // décalage du tableau de tampon
);

// 5e tampon d'attributs : bitangentes
glEnableVertexAttribArray(4);
glBindBuffer(GL_ARRAY_BUFFER, bitangentbuffer);
glVertexAttribPointer(
    4,                                // attribut
    3,                                // taille
    GL_FLOAT,                         // type
    GL_FALSE,                         // normalisé ?
    0,                                // nombre d'octets séparant deux sommets dans le tampon
    (void*)0                          // décalage du tableau de tampon
);

// Tampon d'indices
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer);

// Affiche les triangles !
glDrawElements(
    GL_TRIANGLES,      // mode
    indices.size(),    // nombre
    GL_UNSIGNED_INT,   // type
    (void*)0           // décalage du tableau de tampon
);

glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glDisableVertexAttribArray(2);
glDisableVertexAttribArray(3);
glDisableVertexAttribArray(4);

// Échange les tampons
glfwSwapBuffers();

V-B. Vertex shader

Comme dit précédemment, on va tout faire dans l'espace de la caméra, car il est plus simple d'obtenir la position du fragment dans cet espace. C'est pourquoi on multiplie nos vecteurs T, B et N avec la matrice de modèle-vue.

Sélectionnez
vertexNormal_cameraspace = MV3x3 * normalize(vertexNormal_modelspace);
vertexTangent_cameraspace = MV3x3 * normalize(vertexTangent_modelspace);
vertexBitangent_cameraspace = MV3x3 * normalize(vertexBitangent_modelspace);

Ces trois vecteurs définissent la matrice TBN, qui est créée de cette façon :

Sélectionnez
mat3 TBN = transpose(mat3(
        vertexTangent_cameraspace,
        vertexBitangent_cameraspace,
        vertexNormal_cameraspace
    )); // Vous pouvez utiliser des produits scalaire au lieu de créer cette matrice et de la transposée. Voir les références.

La matrice passe de l'espace de la caméra à l'espace tangent (la même matrice, mais avec XXX_modelspace à la place, permettrai de passer de l'espace modèle à l'espace tangent). On peut l'utiliser pour calculer la direction de la lumière et la direction de l'œil, dans l'espace tangent :

Sélectionnez
LightDirection_tangentspace = TBN * LightDirection_cameraspace;
EyeDirection_tangentspace =  TBN * EyeDirection_cameraspace;

V-C. Fragment shader

La normale, dans l'espace tangent, est immédiate à obtenir, c'est la texture :

Sélectionnez
// Normale locale, dans l'espace tangent
vec3 TextureNormal_tangentspace = normalize(texture2D( NormalTextureSampler, UV ).rgb*2.0 - 1.0);

Donc, on a tout ce dont nous avons besoin. La lumière diffuse utilise clamp(dot(n,l),0,1), avec n et l exprimé dans l'espace tangent (l'espace dans lequel vous effectuez vos produits scalaire et vectoriel n'importe pas ; la chose importante est que l et n soit tous les deux exprimés dans le même espace). La lumière spéculaire utilise clamp(dot(E,R),0, 1), où, encore une fois, E et R sont exprimés dans l'espace tangent. Super !

VI. Résultats

Voici le résultat obtenu. Vous pouvez remarquer que :

  • les briques sont bosselées car on a de nombreuses variations dans les normales ;
  • le ciment semble plat car la texture de normales est complètement bleue.
Application des textures de normales

VII. Aller plus loin

VII-A. Orthogonalisation

Dans le vertex shader on prend la transposée au lieu de l'inverse, car c'est plus rapide. Mais cela ne fonctionne que si l'espace représenté par la matrice est orthogonal, ce qui n'est pas encore le cas. Heureusement, c'est facilement corrigeable : on doit simplement faire que la tangente soit perpendiculaire à la normale à la fin de computeTangentBasis() :

Sélectionnez
t = glm::normalize(t - n * glm::dot(n, t));

La formule peut être difficile à saisir, donc voici un petit schéma pour aider :

Rendre perpendiculaire deux vecteurs

n et t sont presque perpendiculaire, donc on « pousse » t dans la direction de -n multiplié par dot(n,t).

Voici une petite application qui explique aussi cela (utilisez seulement deux vecteurs).

VII-B. Règle de la main droite

Vous n'avez normalement pas à vous en inquiéter, mais dans quelques cas, lorsque vous utilisez des modèles symétriques, les coordonnées UV sont orientées dans le mauvais sens et votre T possède la mauvaise orientation.

Pour vérifier si elle doit être inversée ou pas, c'est simple : TBN doit former un système de coordonnées répondant à la règle de la main droite. Par exemple, cross(n,t) doit avoir la même orientation que b.

En mathématiques, « Un vecteur A à la même orientation qu'un vecteur B » se traduit par dot(A,B) > 0, donc on doit vérifier si dot(cross(n,t),b) > 0.

Si c'est incorrect, inversez t :

Sélectionnez
if (glm::dot(glm::cross(n, t), b) < 0.0f){
    t = t * -1.0f;
}

Cela est aussi effectué pour chaque sommet à la fin de la fonction computeTangentBasis().

VII-C. Texture spéculaire

Pour le fun, j'ai ajouté une texture spéculaire au code. Elle ressemble à cela :

Image non disponible

et je l'ai utilisée pour remplacer le simple gris « vec3(0.3,0.3,0.3) » que l'on utilise pour la couleur spéculaire :

Le ciment est toujours noir : la texture indique qu'il n'y a pas de composante spéculaire.

VII-D. Débogage avec le mode immédiat

L'objectif réel de ce site Web est que vous N'utilisiez PAS le mode immédiat, qui est obsolète, lent et problématique en de nombreux aspects.

Par contre, il arrive qu'il soit très pratique pour le débogage :

Le mode immédiat pour le débogage

Ici, on observe notre espace tangent avec les lignes dessinées dans le mode immédiat.

Pour cela, vous devez abandonner le profil core 3.3 :

Sélectionnez
glfwOpenWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE);

Puis passer les matrices au vieux pipeline OpenGL (vous pouvez aussi écrire un autre shader, mais c'est plus simple de cette façon et vous ête en train de bidouiller de toute façon) :

Sélectionnez
glMatrixMode(GL_PROJECTION);
glLoadMatrixf((const GLfloat*)&ProjectionMatrix[0]);
glMatrixMode(GL_MODELVIEW);
glm::mat4 MV = ViewMatrix * ModelMatrix;
glLoadMatrixf((const GLfloat*)&MV[0]);

Désactiver les shaders :

Sélectionnez
glUseProgram(0);

Et dessiner vos lignes (dans ce cas, les normales, normalisées et multipliées par 0.1 et appliquées au bon sommet) :

Sélectionnez
glColor3f(0,0,1);
glBegin(GL_LINES);
for (int i=0; i<indices.size(); i++){
    glm::vec3 p = indexed_vertices[indices[i]];
    glVertex3fv(&p.x);
    glm::vec3 o = glm::normalize(indexed_normals[indices[i]]);
    p+=o*0.1f;
    glVertex3fv(&p.x);
}
glEnd();

Rappelez-vous : n'utilisez pas le mode immédiat dans une vraie application ! Uniquement pour du débogage ! Et n'oubliez pas de réactiver le profil core après coup, cela vous assurera de ne pas faire de telles choses.

VII-E. Débogage avec les couleurs

Lors du débogage, il peut être utile de visualiser la valeur d'un vecteur. La façon la plus simple pour ce faire est d'écrire sa valeur dans le tampon d'image au lieu de la couleur actuelle. Par exemple, pour visualiser LightDiretion_tangentspace :

Sélectionnez
color.xyz = LightDirection_tangentspace;
Débogage en affichant les vecteurs

Cela signifie que :

  • sur la partie droite du cylindre, la lumière (représentée par la fine ligne blanche) est vers le HAUT (dans l'espace tangent). En d'autres mots, la lumière est dans la direction de la normale des triangles ;
  • sur le milieu du cylindre, la lumière est dans la direction de la tangente (avant+X).

Quelques conseils :

  • suivant ce que vous essayez de voir, vous pouvez souhaiter le normaliser ;
  • si vous ne trouvez pas de signification à ce que vous voyez, visualisez toutes les composantes séparément en forçant par exemple le vert et le bleu à 0 ;
  • évitez de jouer avec l'alpha, c'est trop compliqué Image non disponible ;
  • si vous souhaitez visualiser une valeur négative, vous pouvez utiliser la même astuce que celle pour notre texture de normales : visualisez (v+1.0)/2.0 à la place. Le noir signifie -1 et la couleur +1. Toutefois, c'est difficile d'interpréter le rendu.

VII-F. Débogage avec les noms des variables

Comme dit précédemment, il est important de connaître exactement dans quel espace vos vecteurs se trouvent. Ne faites pas le produit scalaire d'un vecteur situé dans l'espace de la caméra avec un vecteur dans l'espace modèle.

L'ajout de l'espace de chaque sommet à son nom (« ..._modelspace ») aide à la correction de terribles bogues mathématiques.

VII-G. Comment créer une texture de normales

Créé par James O'Hare. Cliquez pour agrandir :

Tutoriel de création d'une texture de normales

VIII. Exercices

  • Normalisez les vecteurs dans indexVBO_TBN avant l'addition et voyez ce que cela fait.
  • Visualisez les autres vecteurs (par exemple, EyeDirection_tangentspace) dans le mode de débogage avec les couleurs et essayez de donner du sens à ce que vous voyez.

IX. Outils et liens

  • Crazybump, un outil super pour faire des textures de normales. Payant.
  • Plugin Nvidia pour Photoshop. Gratuit, mais Photoshop ne l'est pas…
  • Faites vos propres textures de normales à partir de plusieurs photos.
  • Faites vos propres textures de normales à partir d'une photo.
  • Plus d'informations sur la transposition de matrices.

X. Références

  • Lengyel, Eric. “Computing Tangent Space Basis Vectors for an Arbitrary Mesh”. Terathon Software 3D Graphics Library, 2001.
  • Real Time Rendering, troisième édition.
  • ShaderX4.


OpenGL Moderne - tutoriel 14 : rendu dans une texture

Vu dans ... https://opengl.developpez.com/tutoriels/opengl-tutorial/14-rendu-dans-une-texture/

rendu dans une texture

I. Introduction

Le rendu dans une texture (render to texture) est une méthode pratique pour créer une multitude d'effets. L'idée de base est de dessiner une scène, tout comme vous le faisiez habituellement, mais cette fois, dans une texture que vous pouvez réutiliser plus tard.

Les applications de cette technique incluent les caméras dans le jeu, le post-processing et autant d'effets graphiques que vous le souhaitez.

II. Rendu dans une texture

On a trois tâches : créer une texture dans laquelle on va faire le rendu, faire le rendu dans celle-ci et utiliser la texture générée.

II-A. Créer une cible de rendu

La chose dans laquelle on va dessiner s'appelle un tampon d'image (Framebuffer). C'est un conteneur pour les textures et optionnellement un tampon de profondeur. Il est créé exactement comme un autre objet OpenGL :

Sélectionnez
// Le tampon d'image, regroupant 0, 1 ou plus de textures et 0 et 1 tampon de profondeur.
GLuint FramebufferName = 0;
glGenFramebuffers(1, &FramebufferName);
glBindFramebuffer(GL_FRAMEBUFFER, FramebufferName);

Maintenant on doit créer la texture qui va contenir la sortie RGB du shader. Ce code est très classique :

Sélectionnez
// La texture dans laquelle on va dessiner
GLuint renderedTexture;
glGenTextures(1, &renderedTexture);

// "Lie" la nouvelle texture créée : toutes les fonctions suivantes vont modifier cette texture
glBindTexture(GL_TEXTURE_2D, renderedTexture);

// Donne une image vide à OpenGL (le dernier « 0 »)
glTexImage2D(GL_TEXTURE_2D, 0,GL_RGB, 1024, 768, 0,GL_RGB, GL_UNSIGNED_BYTE, 0);

// Filtrage léger. Obligatoire !
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

On doit aussi créer un tampon de profondeur. C'est optionnel et dépend de ce que vous voulez afficher dans votre texture ; mais comme on va afficher Suzanne, on a besoin du test de profondeur.

Sélectionnez
// Le tampon de profondeur
GLuint depthrenderbuffer;
glGenRenderbuffers(1, &depthrenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, depthrenderbuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, 1024, 768);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthrenderbuffer);

Finalement, on configure le tampon d'image :

Sélectionnez
// Définit "renderedTexture" comme notre couleur d'attache #0
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, renderedTexture, 0);

// Définit la liste de tampons à dessiner.
GLenum DrawBuffers[1] = {GL_COLOR_ATTACHMENT0};
glDrawBuffers(1, DrawBuffers); // "1" est la taille de DrawBuffers

Quelque chose a pu mal se passer durant ce processus, selon les capacités du GPU. Voici comment vérifier les erreurs :

Sélectionnez
// Toujours vérifier que notre tampon d'image est ok
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
return false;

II-B. Dessiner dans la texture

Le rendu dans la texture est direct. Liez simplement le tampon d'image et dessinez votre scène comme d'habitude. Facile !

Sélectionnez
// Rendu dans le tampon d'image
glBindFramebuffer(GL_FRAMEBUFFER, FramebufferName);
glViewport(0,0,1024,768); // Rendu dans l'intégralité du tampon d'image, du coin inférieur gauche au coin supérieur droit

Le fragment shader nécessite une adaptation mineure :

Sélectionnez
layout(location = 0) out vec3 color;

Cela signifie que lors de l'écriture dans la variable « color », on va écrire dans la cible de rendu 0, qui correspond à la texture, car DrawBuffers[0] est GL_COLOR_ATTACHMENTi, qui est dans notre cas, renderedTexture.

Pour récapituler :

  • color sera écrit dans le premier tampon à cause du layout(location=0) ;
  • le premier tampon est GL_COLOR_ATTACHMENT0 car DrawBuffers[1] = {GL_COLOR_ATTACHMENT0} ;
  • renderedTexture est attaché à GL_COLOR_ATTACHMENT0, donc c'est où la couleur sera écrite ;
  • en d'autres mots, vous pouvez remplacer GL_COLOR_ATTACHMENT0 par GL_COLOR_ATTACHMENT2 et cela fonctionnera toujours.

Il n'y a pas de layout(location=i) dans OpenGL < 3.3, mais vous pouvez utiliser glFragData[i] = myvalue à la place.

II-C. Utiliser la texture générée

On va dessiner un simple rectangle pour remplir l'écran. On doit utiliser les tampons habituels, shaders, identifiants…

Sélectionnez
// Le rectangle plein écran du FBO
GLuint quad_VertexArrayID;
glGenVertexArrays(1, &quad_VertexArrayID);
glBindVertexArray(quad_VertexArrayID);

static const GLfloat g_quad_vertex_buffer_data[] = {
    -1.0f, -1.0f, 0.0f,
    1.0f, -1.0f, 0.0f,
    -1.0f,  1.0f, 0.0f,
    -1.0f,  1.0f, 0.0f,
    1.0f, -1.0f, 0.0f,
    1.0f,  1.0f, 0.0f,
};

GLuint quad_vertexbuffer;
glGenBuffers(1, &quad_vertexbuffer);
glBindBuffer(GL_ARRAY_BUFFER, quad_vertexbuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(g_quad_vertex_buffer_data), g_quad_vertex_buffer_data, GL_STATIC_DRAW);

// Créer et compile le programme GLSL à partir des shaders
GLuint quad_programID = LoadShaders( "Passthrough.vertexshader", "SimpleTexture.fragmentshader" );
GLuint texID = glGetUniformLocation(quad_programID, "renderedTexture");
GLuint timeID = glGetUniformLocation(quad_programID, "time");

Maintenant vous souhaitez dessiner sur l'écran. Cela se fait en utilisant 0 comme second paramètre de glBindFramebuffer.

Sélectionnez
// Dessiner sur l'écran
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0,0,1024,768); // Dessine sur l'intégralité du tampon d'image, du coin inférieur gauche au coin supérieur droit

On peut dessiner le rectangle plein écran avec un tel shader :

Sélectionnez
#version 330 core

in vec2 UV;

out vec3 color;

uniform sampler2D renderedTexture;
uniform float time;

void main(){
    color = texture( renderedTexture, UV + 0.005*vec2( sin(time+1024.0*UV.x),cos(time+768.0*UV.y)) ).xyz;
}

Ce code échantillonne simplement la texture, mais ajoute un léger décalage dépendant du temps.

III. Résultats

Image non disponible

IV. Aller plus loin

IV-A. Utiliser la profondeur

Dans quelques cas vous pouvez avoir besoin de la profondeur lors de l'utilisation de la texture générée. Dans ce cas, dessinez simplement la texture comme suit :

Sélectionnez
glTexImage2D(GL_TEXTURE_2D, 0,GL_DEPTH_COMPONENT24, 1024, 768, 0,GL_DEPTH_COMPONENT, GL_FLOAT, 0);

(« 24 » est la précision, en bits. Vous pouvez choisir entre 16, 24 et 32, suivant vos besoins. Habituellement 24 suffit.)

Cela devrait être assez pour pouvoir démarrer, mais le code source fourni implémente aussi cela.

Notez que cela devrait être quelque peu plus lent, car le pilote ne sera pas capable d'utiliser quelques optimisations comme Hi-Z.

Dans cette capture d'écran, les niveaux de profondeur sont artificiellement « plus beaux ». Habituellement, c'est beaucoup plus difficile de voir quelque chose sur une texture de profondeur. Proche = Z tendant vers 0 = noir, Lointain = Z tendant vers 1 = blanc.

Suzanne en texture de profondeur

IV-B. Multi-échantillonnage

Vous pouvez écrire dans des textures utilisant le multi-échantillonnage au lieu d'utiliser les textures de « base » : vous devez simplement remplacer glTexImage2D par glTexImage2DMultisample dans le code C++ et sampler2D/texture par sampler2DMS/texelFetch dans le fragment shader.

Par contre, il y a un énorme inconvénient : texelFetch a besoin d'un argument supplémentaire, correspondant au numéro de l'échantillon à récupérer. En d'autres mots, il n'y a pas de méthode automatique de « filtrage » (le terme correct, lorsque l'on parle de multi-échantillonnage, est « résolution »).

Donc, vous pouvez résoudre la texture utilisant le multi-échantillonnage vous-même, dans une autre texture non utilisant le multi-échantillonnage, grâce à un autre shader.

Rien de difficile, mais c'est simplement pénible.

IV-C. Cible de rendu multiples

Vous pouvez écrire dans plusieurs textures en même temps.

Créez simplement plusieurs textures (toutes avec la même et correcte taille !), appelez glFramebufferTexture avec une couleur d'attache différente pour chaque, appelez glDrawBuffers avec des paramètres mis à jour (quelque chose comme (2, {GL_COLOR_ATTACHMENT0,GL_COLOR_ATTACHMENT1})) et ajoutez une autre variable de sortie dans votre fragment shader :

Sélectionnez
layout(location = 1) out vec3 normal_tangentspace; // ou n'importe quoi d'autre

Si vous avez besoin d'écrire un vecteur dans une texture, les textures à virgule flottante existe, avec une précision de 16 ou 32 bits au lieu de 8… voir la documentation de glTexImage2D (cherchez GL_FLOAT).

Dans les versions précédentes d'OpenGL, utilisez glFragData[1] = myvalue à la place.

V. Exercices

  • Essayez d'utiliser glViewport(0,0,512,768); à la place de glViewport(0,0,1024,768); (essayez avec le tampon d'image et l'écran).
  • Expérimentez avec d'autres coordonnées UV dans le dernier fragment shader.
  • Transformez le rectangle avec une vraie matrice de transformation. Codez-la en dur puis essayez d'utiliser les fonctions de controls.hpp ; que remarquez-vous ?


OpenGL Moderne - tutoriel 15 : textures de lumière

Vu dans ... https://opengl.developpez.com/tutoriels/opengl-tutorial/15-textures-de-lumiere/

textures de lumière

I. Introduction

Ce tutoriel est un tutoriel vidéo seulement. Il n'introduit aucune nouvelle technique/syntaxe spécifique à OpenGL, mais vous présente comment utiliser les techniques que vous connaissez déjà pour créer des ombres de grande qualité.

Ce tutoriel explique comment créer un monde simple dans Blender et préparer des textures de lumière afin que vous puissiez les utiliser dans votre application.

Une scène avec des textures de lumière

Aucune connaissance de Blender n'est nécessaire. Je vais expliquer tous les raccourcis clavier et tout ce que je fais.

II. Une note sur les textures de lumière

Les textures de lumière sont précalculées. Une seule fois et pour tout. Cela signifie qu'elles sont complètement statiques, vous ne pouvez pas décider de déplacer une lumière durant l'exécution. Même pas de la supprimer.

Mais cela peut toujours être utile pour la lumière du soleil, ou encore, dans les scènes d'intérieur où vous ne pouvez pas casser d'ampoules. Mirror Edge, publiée en 2009, les utilisent intensivement, que ce soit en extérieur ou en intérieur.

De plus, c'est très simple à mettre en place et vous ne pouvez pas faire plus rapide.

III. La vidéo

C'est une vidéo en 1024 x 768p, utilisez le mode HD…

IV. Appendice

Lors du rendu en OpenGL, vous pouvez remarquer quelques corruptions (ici exagérées) :

Corruption graphique avec les textures de lumière

Cela est dû aux MIP maps, qui mélangent les texels ensemble lorsqu'ils sont visualisés de loin. Les pixels noirs provenant du fond de la texture sont mélangés avec les bonnes parties de la texture de lumière. Pour éviter cela, il y a quelques petits ajustements que vous pouvez faire :

  • vous pouvez demander à Blender de générer une marge autour des limites des coordonnées UV. Cela est accessible à travers le paramètre « margin » dans le panneau « bake ». Pour de bons résultats, vous pouvez aller jusqu'à une marge de 20 pixels ;

  • vous pouvez utiliser un biais dans votre accès à la texture :

    Sélectionnez
    color = texture2D( myTextureSampler, UV, -2.0 ).rgb;

  • -2 est le biais. Vous devez expérimenter avec cette valeur. La capture d'écran ci-dessus a été effectuée avec un biais de +2, ce qui signifie que OpenGL va sélectionner deux MIP maps au-dessus de celle qu'il aurait choisi (donc 16 fois plus petites, provoquant les corruptions) ;

  • vous pouvez remplir le fond noir dans une étape de post-process. Je vais poster plus tard plus d'information là-dessus.


OpenGL Moderne - tutoriel 16 : textures d'ombre (shadow mapping)

Vu dans ... https://opengl.developpez.com/tutoriels/opengl-tutorial/16-textures-ombre-shadow-mapping/

textures d'ombre (shadow mapping)

I. Introduction

Dans le quinzième tutoriel, on a appris à créer des textures de lumière (lightmaps), qui incluent l'éclairage statique. Bien que cela produise de belles ombres, cela ne gère pas les modèles animés.

Les textures d'ombre (shadow maps) sont la technique actuelle (en 2012) pour créer des ombres dynamiques. Le bon point à propos de cela est qu'elles sont relativement faciles à mettre en place. En contrepartie, il est extrêmement difficile de les faire fonctionner correctement.

Dans ce tutoriel, on introduira l'algorithme de base, on verra ses défauts et on implémentera quelques techniques pour obtenir de meilleurs résultats. Sachant qu'au moment de l'écriture (2012), les textures d'ombre sont un sujet de recherche d'actualité, on donnera quelques directions pour vous permettre d'améliorer vos propres textures d'ombre, suivant vos besoins.

II. Textures d'ombre basiques

L'algorithme des textures d'ombre de base se décompose en deux passes. Premièrement, la scène est dessinée à partir de la position de la lumière. Seule la profondeur de chaque fragment est calculée. Ensuite, la scène est affichée comme d'habitude, mais avec un test supplémentaire pour vérifier si le fragment actuel est dans l'ombre.

Le test « être dans l'ombre » est vraiment très simple. Si l'échantillon actuel est plus éloigné de la lumière que la texture d'ombre au même point, cela signifie que la scène contient un objet plus proche de la lumière. En d'autres termes, le fragment actuel est dans l'ombrage.

L'image suivante peut vous aider à comprendre le principe :

Principe du shadow mapping

II-A. Génération de la texture d'ombre

Dans ce tutoriel, on ne considérera que les lumières directionnelles - lumières qui sont si loin que l'on peut considérer les rayons comme étant parallèles. Ainsi, la génération de la texture d'ombre est effectuée avec une matrice de projection orthographique. Une matrice de projection orthographique est exactement comme une matrice de projection en perspective, sauf qu'aucune perspective n'est prise en compte - un objet sera identique indépendamment de sa distance avec la caméra.

II-A-1. Configurer la cible de rendu et la matrice MVP

Depuis le quatorzième tutoriel, vous savez comment dessiner une scène dans une texture afin d'y accéder plus tard à partir d'un shader.

Ici, on utilise une texture de profondeur 1024 x 1024 sur 16 bits pour stocker la texture d'ombre. 16 bits sont généralement suffisants pour une texture d'ombre. Libre à vous d'expérimenter d'autres valeurs.

On utilise une texture de profondeur et non pas une cible de rendu pour la profondeur, car on a besoin de l'échantillonner par la suite.

Sélectionnez
// Le tampon d'image, qui regroupe 0, 1, ou plus de textures et 0 ou 1 tampon de profondeur.
 gluint FramebufferName = 0;
 glGenFramebuffers(1, &FramebufferName);
 glBindFramebuffer(GL_FRAMEBUFFER, FramebufferName);

 // Texture de profondeur. Plus lent qu'un tampon de profondeur, mais vous pouvez l'échantillonner plus tard dans votre shader
 GLuint depthTexture;
 glGenTextures(1, &depthTexture);
 glBindTexture(GL_TEXTURE_2D, depthTexture);
 glTexImage2D(GL_TEXTURE_2D, 0,GL_DEPTH_COMPONENT16, 1024, 1024, 0,GL_DEPTH_COMPONENT, GL_FLOAT, 0);
 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

 glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthTexture, 0);

 glDrawBuffer(GL_NONE); // Aucun tampon de couleur n'est dessiné

 // Toujours vérifier que le tampon d'image est OK
 if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
 return false;

La matrice MVP utilisée pour dessiner la scène à partir de la lumière est calculée comme suit :

  • la matrice de projection est une matrice orthographique incluant tout ce qui est compris dans une boîte alignée aux axes de (-10, 10), (-10, 10), (-10, 20) sur les axes X, Y et Z, respectivement. Ces valeurs ont été choisies pour que toute la partie visible de la scène soit toujours visible ; plus d'informations sur ce point dans la section, voir plus loin ;
  • la matrice de vue tourne le monde afin que, dans l'espace caméra, la direction de la lumière soit -Z (aimeriez-vous relire le troisième tutoriel ?) ;
  • la matrice de modèle est ce que vous souhaitez.
Sélectionnez
glm::vec3 lightInvDir = glm::vec3(0.5f,2,2);

// Calcule la matrice MVP à partir du point de vue de la lumière
glm::mat4 depthProjectionMatrix = glm::ortho<float>(-10,10,-10,10,-10,20);
glm::mat4 depthViewMatrix = glm::lookAt(lightInvDir, glm::vec3(0,0,0), glm::vec3(0,1,0));
glm::mat4 depthModelMatrix = glm::mat4(1.0);
glm::mat4 depthMVP = depthProjectionMatrix * depthViewMatrix * depthModelMatrix;

// Envoie les transformations au shader actuellement lié, dans la variable uniforme "MVP"
glUniformMatrix4fv(depthMatrixID, 1, GL_FALSE, &depthMVP[0][0])

II-A-2. Les shaders

Les shaders utilisés durant cette passe sont très simples. Le vertex shader est un shader passant les données au fragment shader qui calcule simplement la position des sommets en coordonnées homogènes :

Sélectionnez
#version 330 core

// Données d'entrées du sommet, différentes pour toutes les exécutions de ce shader.
layout(location = 0) in vec3 vertexPosition_modelspace;

// Valeurs qui restent constantes pour le modèle entier.
uniform mat4 depthMVP;

void main(){
 gl_Position =  depthMVP * vec4(vertexPosition_modelspace,1);
}

Le fragment shader est tout aussi simple : il écrit la profondeur du fragment à l'emplacement 0 (c'est-à-dire dans la texture de profondeur).

Sélectionnez
#version 330 core

// Données de sortie
layout(location = 0) out float fragmentdepth;

void main(){
    // Pas vraiment nécessaire, OpenGL le fait de toute façon
    fragmentdepth = gl_FragCoord.z;
}

Le rendu d'une texture d'ombre est généralement deux fois plus rapide qu'un rendu normal, car seule une profondeur en faible précision est écrite, à la place de la profondeur et de la couleur. La bande passante mémoire est souvent le plus grand problème de performance sur les GPU.

II-A-3. Résultat

La texture ressemble à ceci :

Une texture de profondeur

Une couleur sombre signifie un petit z ; donc, le coin supérieur droit du mur est proche de la caméra. Au contraire, le blanc signifie z=1 (en coordonnées homogènes), donc que l'élément est très loin.

II-B. Utiliser la texture d'ombre

II-B-1. Shader de base

Maintenant, on retourne à notre shader habituel. Pour chaque fragment que l'on calcule, on doit tester s'il se trouve « derrière » la texture d'ombre ou non.

Pour ce faire, on doit calculer la position actuelle du fragment dans le même espace que celui que nous avons utilisé lors de la création de la texture d'ombre. Donc, on doit le transformer avec la matrice MVP habituelle et une seconde fois avec la matrice MVP de la passe de profondeur (depthMVP).

Il y a tout de même une petite astuce. La multiplication de la position du sommet avec la matrice depthMVP donnera des coordonnées homogènes, qui sont comprises entre [-1, 1]. Mais l'échantillonnage d'une texture doit être fait entre [0, 1].

Par exemple, un fragment au milieu de l'écran sera en (0, 0) dans l'espace de coordonnées homogènes. Mais comme il devra correspondre au milieu de la texture, les coordonnées UV devront être (0.5, 0.5).

Cela peut être corrigé en ajustant les coordonnées directement dans le fragment shader, bien qu'il soit plus efficace de multiplier les coordonnées homogènes avec la matrice suivante, qui divise simplement les coordonnées par 2 (la diagonale : [-1, 1] → [-0.5, 0.5]) et les déplace (la ligne du bas : [-0.5, 0.5] → [0, 1]).

Sélectionnez
glm::mat4 biasMatrix(
0.5, 0.0, 0.0, 0.0,
0.0, 0.5, 0.0, 0.0,
0.0, 0.0, 0.5, 0.0,
0.5, 0.5, 0.5, 1.0
);
glm::mat4 depthBiasMVP = biasMatrix*depthMVP;

On peut maintenant écrire le vertex shader. Il est identique au précédent, mais on sort deux positions au lieu d'une :

  • gl_Position est la position du sommet tel qu'il est vu par la caméra ;
  • ShadowCoord est la position du sommet tel qu'il est vu à partir de l'ancienne caméra (la lumière).
Sélectionnez
// Position de sortie du sommet, dans l'espace clip : MVP * position
gl_Position =  MVP * vec4(vertexPosition_modelspace,1);

// Pareil, mais avec la matrice de vue de la lumière
ShadowCoord = DepthBiasMVP * vec4(vertexPosition_modelspace,1);

Le fragment shader est très simple :

  • texture(shadowMap, ShadowCoord.xy).z est la distance entre la lumière et l'objet le plus proche ;
  • ShadowCoord.z est la distance entre la lumière et le fragment actuel.

Donc si le fragment actuel est plus loin que l'objet le plus proche, cela signifie que l'on se trouve dans l'ombre (du plus proche objet) :

Sélectionnez
float visibility = 1.0;
if ( texture( shadowMap, ShadowCoord.xy ).z  <  ShadowCoord.z){
    visibility = 0.5;
}

On a juste besoin d'utiliser cela pour modifier l'ombrage. Bien sûr, la couleur ambiante n'est pas modifiée, car son but est d'imiter une lumière arrivant même lorsqu'on se trouve dans l'ombrage (ou sinon tout serait complètement noir).

Sélectionnez
color =
 // Ambiante : simule l'éclairage indirect
 MaterialAmbientColor +
 // Diffuse : "coleur" de l'objet
 visibility * MaterialDiffuseColor * LightColor * LightPower * cosTheta+
 // Spéculaire : surbrillance réflective, comme un miroir
 visibility * MaterialSpecularColor * LightColor * LightPower * pow(cosAlpha,5);

II-B-2. Résultat - acné d'ombre

Voici le résultat du code actuel. Évidemment, l'idée générale est présente, mais la qualité est inacceptable.

Premier essai avec les textures d'ombre

Regardez chacun des problèmes de l'image. Le code possède deux projets : shadowmaps et shadowmaps_simple : commencez par celui que vous préférez. La version simple est tout aussi laide que l'image ci-dessus, mais plus facile à comprendre.

III. Problèmes

III-A. Acné de l'ombrage

Le problème le plus évident s'appelle acné de l'ombrage :

Acné de l'ombrage

Ce phénomène est facilement explicable avec une image :

Explication de l'acné d'ombrage

Le « correctif » habituel pour cela consiste à utiliser une marge d'erreur : on n'ajoute l'ombre que si la profondeur du fragment actuel (encore une fois, dans l'espace de la lumière) est réellement loin de la valeur de la texture de lumière. On fait cela en ajoutant un biais :

Sélectionnez
float bias = 0.005;
float visibility = 1.0;
if ( texture2D( shadowMap, ShadowCoord.xy ).z  <  ShadowCoord.z-bias){
    visibility = 0.5;
}

Le résultat est déjà beaucoup plus beau :

Suppression de l'acné d'ombrage

Par contre, vous pouvez remarquer qu'à cause de notre biais, l'artefact entre le sol est le mur s'est empiré. Qui plus est, un biais de 0.005 semble trop important pour le sol, mais pas assez pour les surfaces arrondies : quelques artefacts sont toujours visibles sur le cylindre et la sphère.

Une approche commune consiste à modifier le biais suivant la pente :

Sélectionnez
float bias = 0.005*tan(acos(cosTheta)); // cosTheta est dot( n,l ), réduit entre 0 et 1
bias = clamp(bias, 0,0.01);

L'acné d'ombrage n'est plus là, même sur les surfaces arrondies :

Suppression de l'acné d'ombrage avec un biais variable

Une autre astuce qui peut ou non fonctionner suivant vos modèles est d'afficher seulement les faces arrière dans la texture d'ombre. Cela force à utiliser une scène spécifique (voir la prochaine section - Peter Panning) avec les murs fins. Mais au moins, l'acné sera sur les surfaces qui sont dans l'ombre :

Schéma d'ombrage des faces arrières

Lors du rendu de la texture d'ombre, supprimez les faces avant des triangles :

Sélectionnez
        // On n'utilise pas de biais dans le shader, mais à la place on dessine les faces arrière,
        // qui sont déjà séparées des faces avec une faible distance
        // (si la scène est construite de cette façon)
        glCullFace(GL_FRONT); // Suppression des faces avant des triangles -> ne dessine que les faces arrière

Et ensuite, affichez la scène avec un rendu normal (suppression des faces arrière) :

Sélectionnez
glCullFace(GL_BACK); // Suppression des faces arrière des triangles → dessine seulement les faces avant

Cette méthode est utilisée dans le code, en plus du biais.

III-B. Peter Panning

On n'a plus d'acné d'ombrages, mais on a toujours un mauvais ombrage sur le sol, faisant comme si les murs volaient (d'où le terme « Peter Panning »). En fait, en ajoutant le biais, c'est devenu pire.

Zoom sur le Peter Panning

Celui-ci est très facile à corriger : évitez tout simplement les géométries fines. Cela a deux avantages :

  • premièrement, cela corrige le « Peter Panning » : si votre géométrie est plus profonde que le biais, tout est bon ;
  • deuxièmement, vous pouvez réactiver la suppression des faces arrière lors du rendu de la texture de lumière, car maintenant, il y a un polygone du mur qui fait face à la lumière et donc cache l'autre côté, qui n'aurait pas été affiché avec la suppression des faces arrière.

L'inconvénient est que vous avez plus de triangles à afficher (deux fois par image !).

Scène sans Peter Panning

III-C. Crénelage

Mais avec ces deux astuces, vous allez remarquer qu'il y a toujours du crénelage sur le bord de l'ombre. En d'autres termes, un pixel est blanc et le prochain noir, sans même de transition douce entre les deux.

Exemple de crénelage sur l'ombre

III-C-1. PCF

La façon la plus simple d'améliorer cela est de changer le type d'échantillonnage de la texture d'ombre en shadow2DShadow. La conséquence est que, lorsque vous échantillonnez une fois, le matériel fera en réalité aussi un échantillonnage des pixels voisins, une comparaison entre eux et retournera un nombre à virgule flottante compris dans [0, 1] avec un filtrage bilinéaire du résultat de la comparaison.

Par exemple, 0.5 signifie que deux échantillons sont dans l'ombre et deux échantillons dans la lumière.

Ce n'est pas la même chose qu'un échantillonnage simple d'une texture de profondeur filtrée ! Une comparaison retourne toujours vrai ou faux ; PCF donne une interpolation de quatre « vrai ou faux ».

Exemple de PCF

Comme vous pouvez le voir, les bordures de l'ombre sont douces, mais la texture d'ombre est toujours visible.

III-C-2. Échantillonnage poisson

Une méthode simple pour le gérer est d'échantillonner la texture d'ombre N fois au lieu d'une seule. En combinaison avec le PCF, cela peut donner de très bons résultats, même avec un petit N. Voici le code pour quatre échantillonnages :

Sélectionnez
for (int i=0;i<4;i++){
  if ( texture2D( shadowMap, ShadowCoord.xy + poissonDisk[i]/700.0 ).z  <  ShadowCoord.z-bias ){
    visibility-=0.2;
  }
}

poissonDisk est un tableau constant qui peut être défini comme suit :

Sélectionnez
vec2 poissonDisk[4] = vec2[](
  vec2( -0.94201624, -0.39906216 ),
  vec2( 0.94558609, -0.76890725 ),
  vec2( -0.094184101, -0.92938870 ),
  vec2( 0.34495938, 0.29387760 )
);

De cette façon, suivant le nombre de passes d'échantillonnage de la texture d'ombre, le fragment généré sera plus ou moins sombre :

La constante 700.0 définit la « diffusion » des échantillons. Si la diffusion est trop petite, vous obtenez du crénelage ; si la diffusion est trop importante, vous obtenez des bandes (cette capture n'utilise pas PCF pour accentuer l'effet, mais utilise 16 échantillons à la place).

Un effet de bande sur les ombres
Effet de bandes sur la scène

III-C-3. Échantillonnage poisson stratifié

On peut supprimer cet effet de bande en choisissant différents échantillons pour chaque pixel. Il y a deux méthodes principales : le poisson stratifié ou le poisson tourné. Le stratifié choisit différents échantillons, la rotation toujours les mêmes, mais avec une rotation aléatoire afin qu'ils semblent différents. Dans ce tutoriel, je vais expliquer seulement la version stratifiée.

La seule différence avec la version précédente est que l'on indexe poissonDisk avec un indice aléatoire :

Sélectionnez
    for (int i=0;i<4;i++){
        int index = // Un nombre aléatoire entre 0 et 15, différent pour chaque pixel (et chaque i !)
        visibility -= 0.2*(1.0-texture( shadowMap, vec3(ShadowCoord.xy + poissonDisk[index]/700.0,  (ShadowCoord.z-bias)/ShadowCoord.w) ));
    }

On peut générer un nombre aléatoire avec une ligne comme celle-ci, qui retourne un nombre entre [0, 1[ :

Sélectionnez
    float dot_product = dot(seed4, vec4(12.9898,78.233,45.164,94.673));
    return fract(sin(dot_product) * 43758.5453);

Dans ce cas, seed4 sera une combinaison de i (faisant que l'on échantillonne à quatre emplacements différents) et… quelque chose d'autre. On peut utiliser gl_FragCoord (l'emplacement du pixel sur l'image) ou Position_worldspace :

Sélectionnez
        //  - Un échantillon aléatoire, basé sur l'emplacement du pixel sur l'écran.
        //    Pas de bandes, mais l'ombre se déplace avec la caméra, ce qui est étrange.
        int index = int(16.0*random(gl_FragCoord.xyy, i))%16;
        //  - Un échantillon aléatoire, basé sur la position du pixel dans l'espace monde.
        //    La position est arrondie au millimètre pour éviter le crénelage.
        //int index = int(16.0*random(floor(Position_worldspace.xyz*1000.0), i))%16;

Cela fera que les motifs de l'image ci-dessus vont disparaître, au détriment d'un bruit visuel. Bien qu'un bruit correctement appliqué soit souvent moins désagréable que ces motifs.

Ombre avec du bruit

Voir le fichier tutorial16/ShadowMapping.fragmentshader pour les trois exemples d'implémentation.

IV. Aller plus loin

Même avec toutes ces astuces, il reste de nombreuses, très nombreuses méthodes pour améliorer vos ombres. Voici les plus répandues :

IV-A. Early bailing

Au lieu de prendre seize échantillons pour chaque fragment (encore une fois, c'est beaucoup), on prend quatre échantillons distants. S'ils sont tous dans la lumière ou dans l'ombre, vous pouvez sûrement considérer que les seize échantillons auraient donné le même résultat : vous pouvez arrêter tout de suite (bail early). Si certains sont différents, vous êtes probablement sur une bordure d'ombre, donc les seize sont nécessaires.

IV-B. Lumières spots

La gestion des lumières spots nécessite quelques petites modifications. La plus évidente est de changer la matrice de projection orthographique entre une matrice de projection en perspective :

Sélectionnez
glm::vec3 lightPos(5, 20, 20);
glm::mat4 depthProjectionMatrix = glm::perspective<float>(45.0f, 1.0f, 2.0f, 50.0f);
glm::mat4 depthViewMatrix = glm::lookAt(lightPos, lightPos-lightInvDir, glm::vec3(0,1,0));

Même chose, mais avec une pyramide tronquée de matrice de perspective à la place de celle de la matrice orthographique. Utilisez texture2Dproj pour prendre en compte la division de la perspective (voir les notes de bas de pages du troisième tutoriel sur les matrices).

La seconde étape consiste à prendre en compte la perspective dans le shader (voir les notes de bas de pages du troisième tutoriel sur les matrices). En résumé, une matrice de projection perspective n'a en fait pas besoin d'effectuer de perspective. Cela est effectué par le matériel, lors de la division avec la coordonnée projetée w. Ici, on émule la transformation dans le shader, donc on doit effectuer la division de la perspective. D'ailleurs, une matrice orthographique génère toujours des vecteurs homogènes avec w = 1, faisant qu'elle ne produit jamais de perspective.

Voici deux façons de faire cela en GLSL. La seconde utilise la fonction du langage textureProj, mais les deux méthodes produisent le même résultat.

Sélectionnez
if ( texture( shadowMap, (ShadowCoord.xy/ShadowCoord.w) ).z  <  (ShadowCoord.z-bias)/ShadowCoord.w )
if ( textureProj( shadowMap, ShadowCoord.xyw ).z  <  (ShadowCoord.z-bias)/ShadowCoord.w )

IV-C. Lumières ponctuelles

Même chose, mais avec une carte cubique (cubemap) de profondeur. Une carte cubique est un ensemble de six textures, une pour chaque côté du cube. De plus, l'accès ne se fait pas avec des coordonnées UV standard, mais avec un vecteur 3D représentant une direction.

La profondeur est conservée pour chaque direction dans l'espace, rendant possible la projection des ombres tout autour de la lumière ponctuelle.

IV-D. Combinaison de plusieurs lumières

L'algorithme gère plusieurs lumières, mais gardez à l'esprit que chaque lumière nécessite un rendu supplémentaire de la scène afin de produire la carte d'ombres. Cela nécessitera une quantité énorme de mémoire lors de l'application des ombres et vous pouvez être très rapidement limité par la bande passante.

IV-E. Zone automatique de la lumière

Dans ce tutoriel, la zone de lumière est produite à la main pour contenir toute la scène. Bien que cela fonctionne dans cet exemple restreint, c'est à éviter. Si votre carte s'étend sur 1 km x 1 km, chaque texel de votre carte d'ombres 1024 x 1024 prendra un mètre carré, ce qui est nul. La matrice de projection de la lumière doit être aussi compacte que possible.

Pour les lumières spots, cela peut être facilement modifié en jouant sur sa portée.

Pour les lumières directionnelles, comme le soleil, c'est plus compliqué : elles illuminent réellement toute la scène. Voici une façon de calculer cette pyramide tronquée :

  1. Les Potential Shadow Receivers, ou PSR, sont des objets qui appartiennent en même temps à la zone de lumière, à celle de la vue et à la boîte englobante de la scène. Comme leur nom le suggère, ces objets sont susceptibles d'être dans l'ombre : ils sont visibles par la caméra et la lumière ;
  2. Les Potential Shadow Casters, ou PSC, sont tous les Potential Shadow Receivers, plus tous les objets qui se tiennent entre eux et la lumière (un objet peut ne pas être visible mais produire une ombre visible).

Donc, pour calculer la matrice de projection de la lumière, prenez tous les objets visibles, retirez ceux qui sont trop loin et calculez leur boîte englobante. Ajoutez les objets se tenant entre la boîte englobante et la lumière, et calculez la nouvelle boîte englobante (mais cette fois, alignée suivant la direction de la lumière).

Le calcul précis de ces ensembles implique le calcul d'intersections d'enveloppes convexes, mais cette méthode est bien plus facile à implémenter.

Cette méthode provoquera des apparitions soudaines lorsque les objets disparaîtrons de la zone, car la résolution de la texture d'ombre diminuera brusquement. Les textures d'ombre en cascade ne souffrent pas de ce problème, mais sont plus compliquées à implémenter et vous pouvez toujours compenser cela en adoucissant les valeurs sur le temps.

IV-F. Cartes d'ombres exponentielles

Les cartes d'ombres exponentielles tentent de limiter le crénelage en supposant qu'un fragment se situant dans l'ombre, mais proche de la surface éclairée, est en fait « quelque part entre les deux ». Cela est lié au biais, sauf que le test n'est plus binaire : le fragment devient plus sombre lorsque la distance de la surface éclairée augmente.

C'est de la triche, évidemment, et des artefacts peuvent apparaître lorsque deux objets se recouvrent.

IV-G. Light-space perspective Shadow Maps

Les LiSPSM ajustent la matrice de projection de la lumière afin d'obtenir une plus grande précision pour les objets proches de la caméra. C'est très important dans les cas de « duelling frustra » : vous regardez dans une direction, mais la lumière spot « semble » dans la direction opposée. Vous avez une grande précision pour la carte d'ombres près de la lumière, soit loin de vous et une faible résolution proche de la caméra, là où vous en avez le plus besoin.

Par contre, les LiSPSM sont délicates à implémenter. Lisez les références pour des détails d'implémentation.

IV-H. Cartes d'ombres en cascade

Les CSM gèrent le même problème que les LiSPSM mais d'une manière différente. Elles utilisent simplement plusieurs (2-4) cartes d'ombres standards pour les différentes parties de la zone vue. La première gère les premiers mètres, donc vous allez obtenir une bonne résolution pour une petite zone. La prochaine carte d'ombres gère les objets plus loin. La dernière carte d'ombres gère la grosse partie de la scène, mais à cause de la perspective, elle ne sera pas aussi importante visuellement que la zone la plus proche.

Les cartes d'ombres en cascade ont, au moment de l'écriture (2012), le meilleur compromis complexité/qualité. C'est la solution dans bien des cas.

V. Conclusion

Comme vous pouvez le voir, les cartes d'ombres sont un sujet compliqué. Chaque année, de nouvelles variations et améliorations sont publiées, et aujourd'hui, aucune solution n'est parfaite.

Heureusement, les solutions présentées peuvent être mélangées : il est parfaitement possible d'avoir des cartes d'ombres en cascade dans une perspective dans l'espace lumière, adoucie avec le PCF… Essayez d'expérimenter toutes ces techniques.

Comme conclusion, je vous suggère de rester avec les cartes de lumières précalculées autant que possible et d'utiliser les cartes d'ombres seulement pour les objets dynamiques. Et assurez-vous que la qualité visuelle des deux soit équivalente : il n'est pas bon d'avoir un environnement statique parfait et des ombres dynamiques atroces.


< OpenGL Moderne - tutoriel 17 : les rotations

Vu dans ... https://opengl.developpez.com/tutoriels/opengl-tutorial/17-les-rotations-quaternions/

les rotations

I. Introduction

Ce tutoriel sort du contexte d'OpenGL, mais s'attaque néanmoins à un problème très courant : comment représenter les rotations ?

Dans le troisième tutoriel - les matrices, on a appris que celles-ci sont capables de tourner un point autour d'un axe donné. Bien que les matrices offrent une méthode propre pour transformer les sommets, leur gestion est difficile : comme, par exemple, l'obtention de la matrice de rotation à partir de la matrice finale.

On présentera les deux manières les plus courantes pour définir des rotations : les angles d'Euler et les quaternions. Et le plus important, on expliquera pourquoi vous devriez utiliser les quaternions.

Tutoriel OpenGL moderne 17

II. Avant-propos : rotation VS orientation

En lisant des articles sur les rotations, vous pouvez être confus face au vocabulaire. Dans ce tutoriel :

  • une orientation est un état : « l'orientation de l'objet est… » ;
  • une rotation est une opération : « appliquer cette rotation à l'objet ».

Lorsque vous appliquez une rotation, vous changez l'orientation. Les deux peuvent être représentés avec les mêmes outils, menant à la confusion. Maintenant, on y va…

III. Angles d'Euler

Les angles d'Euler sont la méthode la plus simple de penser une orientation. Vous stockez trois orientations autour des axes X, Y et Z. C'est un concept très facile à comprendre. Vous pouvez utiliser un vec3 pour stocker la rotation :

Sélectionnez
vec3 EulerAngles( RotationAroundXInRadians, RotationAroundYInRadians, RotationAroundZInRadians);

Ces trois rotations sont ensuite appliquées successivement, habituellement dans cet ordre : en premier Y, puis Z et X (mais pas obligatoirement). L'utilisation d'un autre ordre entraîne d'autres résultats.

Une utilisation simple des angles d'Euler est l'orientation d'un personnage. Habituellement, les personnages de jeux ne tournent pas autour des axes X et Z, seulement autour de l'axe vertical. Donc, il est plus simple d'écrire, comprendre et maintenir float direction; que trois orientations différentes.

Une autre bonne utilisation des angles d'Euler correspond à une caméra FPS : vous avez un angle pour le cap (Y) et un pour le haut/bas (X). Jetez un œil au fichier common/controls.cpp pour en avoir un exemple.

Cependant, lorsque les choses deviennent plus complexes, les angles d'Euler rendront le travail difficile. Par exemple :

  • l'interpolation douce entre deux orientations est compliquée. L'interpolation naïve des angles autour de X, Y et Z sera horrible ;
  • l'application de plusieurs rotations est compliquée et inexacte : vous devez calculer la matrice de rotation finale et deviner les angles d'Euler de cette matrice ;
  • un problème bien connu, le « blocage de cardan » (« Gimbal lock »), bloquera quelques fois vos rotations et d'autres singularités retourneront votre modèle à l'envers ;
  • des angles différents donnent la même rotation (-180° et 180°, par exemple) ;
  • c'est un bazar ; comme dit précédemment, le bon ordre est habituellement YZX, mais si vous utilisez une bibliothèque avec un autre ordre, vous allez avoir des soucis ;
  • quelques opérations sont complexes : par exemple, la rotation de N degrés autour d'un axe spécifique.

Les quaternions sont l'outil qui résout ces problèmes pour représenter les rotations.

IV. Quaternions

Un quaternion est un ensemble de quatre nombres, [x, y, z, w], qui représentent les rotations de la façon suivante :

Sélectionnez
// RotationAngle est en radians
x = RotationAxis.x * sin(RotationAngle / 2)
y = RotationAxis.y * sin(RotationAngle / 2)
z = RotationAxis.z * sin(RotationAngle / 2)
w = cos(RotationAngle / 2)

RotationAxis est, comme son nom l'indique, l'axe autour duquel vous souhaitez effectuer la rotation.

RotationAngle est l'angle de rotation autour de cet axe.

Schéma de quaternion

Donc fondamentalement, les quaternions stockent un axe de rotation et un angle de rotation, d'une façon simplifiant la combinaison des rotations.

IV-A. Lire des quaternions

Le format est définitivement moins intuitif que celui des angles d'Euler, mais il reste lisible : les composantes xyz correspondent grossièrement à l'axe de rotation et w est l'arc cosinus de l'angle de rotation (divisé par 2). Par exemple, imaginez que vous voyez les valeurs suivantes dans le débogueur : [0.7, 0, 0, 0.7]. x = 0.7, c'est plus grand que y et z, donc vous savez grossièrement que la rotation est principalement autour de l'axe X, et 2 * acos(0.7) = 1.59 radians, donc que la rotation est de 90°.

Pareillement, [0, 0, 0, 1] (w = 1) signifie que l'angle = 2 * acos(1) = 0, donc que c'est un quaternion unitaire, ne faisant aucune rotation.

IV-B. Opérations de bases

Connaître les mathématiques derrière les quaternions n'est que rarement utile : la représentation est tellement non intuitive que vous reposez habituellement sur les fonctions utilitaires qui feront les calculs pour vous. Si vous êtes intéressés, regardez les livres de mathématiques dans la page des outils et liens utiles.

IV-B-1. Comment créer un quaternion en C++ ?

Sélectionnez
// N'oubliez pas #include <glm/gtc/quaternion.hpp> et <glm/gtx/quaternion.hpp>

// Créer un quaternion unitaire (aucune rotation)
quat MyQuaternion;

// Spécification directe des quatre composantes
// Vous ne faites presque jamais cela directement
MyQuaternion = quat(w,x,y,z);

// Conversion à partir des angles d'Euler (en radians) vers un quaternion
vec3 EulerAngles(90, 45, 0);
MyQuaternion = quat(EulerAngles);

// Conversion à partir d'un axe/angle
// Dans GLM l'angle doit être en degrés. Ici, donc, convertissez-le.
MyQuaternion = gtx::quaternion::angleAxis(degrees(RotationAngle), RotationAxis);

IV-B-2. Comment créer un quaternion en GLSL ?

Vous ne le faites pas. Convertissez le quaternion vers une matrice de rotation et utilisez-la dans la matrice de modèle. Vos sommets seront tournés comme d'habitude, avec la matrice MVP.

Dans quelques cas, vous pouvez réellement vouloir utiliser les quaternions en GLSL, par exemple si vous faites l'animation d'un squelette sur le GPU. Il n'y a pas de type pour les quaternions en GLSL, mais vous pouvez utiliser un vec4 et faire les mathématiques vous-même dans le shader.

IV-B-3. Comment convertir un quaternion vers une matrice ?

Sélectionnez
mat4 RotationMatrix = quaternion::toMat4(quaternion);

Vous pouvez maintenant construire votre matrice de modèle comme d'habitude :

Sélectionnez
mat4 RotationMatrix = quaternion::toMat4(quaternion);
...
mat4 ModelMatrix = TranslationMatrix * RotationMatrix * ScaleMatrix;
// Vous pouvez maintenant utiliser ModelMatrix pour construire la matrice MVP

V. Donc, quelle méthode choisir ?

Choisir entre les angles d'Euler et les quaternions est difficile. Les angles d'Euler sont intuitifs pour les artistes, donc si vous écrivez un éditeur 3D, utilisez-les. Mais les quaternions sont pratiques pour les programmeurs et aussi plus rapides, donc vous devriez les utiliser dans le cœur du moteur 3D.

Le consensus général est exactement cela : utilisez les quaternions en interne et exposez les angles d'Euler à chaque fois que vous avez une interface utilisateur.

Vous allez être capable de gérer tout ce que vous voulez (ou du moins, cela sera plus facile) et vous pouvez toujours utiliser les angles d'Euler pour les entités le nécessitant (comme dit précédemment : la caméra, les humanoïdes et c'est à peu près tout) avec une simple conversion.

VI. Autres ressources

  1. Les livres dans la page des outils et liens utiles ;
  2. Aussi vieux qu'il puisse être, le Game Programming Gem 1 contient quelques articles géniaux sur les quaternions. Vous pouvez sûrement le trouver en ligne ;
  3. Une présentation de la GDC sur les rotations ;
  4. Le tutoriel sur les quaternions du wiki des programmeurs de jeux vidéo ;
  5. La FAQ sur les quaternions d'Ogre, bien que la seconde partie soit principalement spécifique à Ogre ;
  6. Les fichiers d'Ogre Vertor3D.h et Quaternions.cpp.

VII. Feuille de triche

VII-A. Comment savoir que deux quaternions sont similaires ?

Lors de l'utilisation d'un vecteur, le produit scalaire donne le cosinus de l'angle entre ces deux vecteurs. Si cette valeur est 1, alors les vecteurs sont dans la même direction.

Avec les quaternions, c'est la même chose :

Sélectionnez
float matching = quaternion::dot(q1, q2);
if ( abs(matching-1.0) < 0.001 ){
    // q1 et q2 sont similaires
}

Vous pouvez aussi obtenir l'angle entre q1 et q2 en prenant le acos() de ce produit scalaire.

VII-B. Comment appliquer une rotation sur un point ?

Vous pouvez faire comme suit :

Sélectionnez
rotated_point = orientation_quaternion * point;

…mais si vous souhaitez calculer votre matrice de modèle, vous devriez probablement convertir le quaternion en une matrice.

Le centre de la rotation est toujours l'origine.

Si vous souhaitez tourner autour d'un autre point :

Sélectionnez
rotated_point = origin + (orientation_quaternion * (point-origin));

VII-C. Comment interpoler entre deux quaternions ?

Cela s'appelle SLERP : Sphérical Liner intERPolation. Avec GLM, vous pouvez l'appliquer avec la fonction mix :

Sélectionnez
glm::quat interpolatedquat = quaternion::mix(quat1, quat2, 0.5f); // ou n'importe quel autre facteur

VII-D. Comment accumuler deux rotations ?

C'est simple ! Multipliez les deux quaternions ensemble. L'ordre est identique à celui des matrices, c'est-à-dire l'inverse :

Sélectionnez
quat combined_rotation = second_rotation * first_rotation;

VII-E. Comment trouver la rotation entre deux vecteurs ?

(En d'autres mots : le quaternion doit tourner v1 pour qu'il corresponde à v2.)

L'idée de base est évidente :

  • l'angle entre deux vecteurs est facilement trouvable : le produit scalaire donne son cosinus ;
  • l'axe nécessaire est aussi facilement trouvable : c'est le produit vectoriel des deux vecteurs.

L'algorithme suivant fait exactement cela, mais gère aussi quelques cas spéciaux :

Sélectionnez
quat RotationBetweenVectors(vec3 start, vec3 dest){
    start = normalize(start);
    dest = normalize(dest);

    float cosTheta = dot(start, dest);
    vec3 rotationAxis;

    if (cosTheta < -1 + 0.001f){
        // cas spécifique lorsque les vecteurs ont des directions opposées :
        // il n'y pas d'axe de rotation "idéal"
        // Donc, devinez-en un, n'importe lequel fonctionnera tant qu'il est perpendiculaire avec start
        rotationAxis = cross(vec3(0.0f, 0.0f, 1.0f), start);
        if (gtx::norm::length2(rotationAxis) < 0.01 ) // pas de chance, ils sont parallèles, essayez encore !
            rotationAxis = cross(vec3(1.0f, 0.0f, 0.0f), start);

        rotationAxis = normalize(rotationAxis);
        return gtx::quaternion::angleAxis(180.0f, rotationAxis);
    }

    rotationAxis = cross(start, dest);

    float s = sqrt( (1+cosTheta)*2 );
    float invs = 1 / s;

    return quat(
        s * 0.5f,
        rotationAxis.x * invs,
        rotationAxis.y * invs,
        rotationAxis.z * invs
    );

}

(Vous pouvez trouver cette fonction dans common/quaternion_utils.cpp.)

VII-F. J'ai besoin d'un équivalent à gluLookAt. Comment orienter un objet vers un point ?

Utilisez RotationBetweenVectors !

Sélectionnez
// Trouve la rotation entre l'avant de l'objet (que nous supposons vers +Z,
// mais cela dépend de votre modèle) et la direction voulue
quat rot1 = RotationBetweenVectors(vec3(0.0f, 0.0f, 1.0f), direction);

Maintenant, vous pouvez aussi souhaiter que votre objet soit droit :

Sélectionnez
// Recalcule desiredUp afin qu'il soit perpendiculaire à la direction
// Vous pouvez passer cette partie si vous souhaitez forcer desiredUp
vec3 right = cross(direction, desiredUp);
desiredUp = cross(right, direction);

// À cause de la première rotation, le haut est probablement complètement retourné.
// Trouve la rotation entre "up" de l'objet tourné et le haut voulu
vec3 newUp = rot1 * vec3(0.0f, 1.0f, 0.0f);
quat rot2 = RotationBetweenVectors(newUp, desiredUp);

Maintenant, combinez-les :

Sélectionnez
quat targetOrientation = rot2 * rot1; // rappelez-vous, dans l'ordre inverse.

Attention, « direction » est, bien sûr, une direction et non la position cible ! Vous pouvez calculer la direction simplement avec : targetPos - currentPos.

Une fois que vous avez l'orientation cible, vous allez probablement souhaiter effectuer une interpolation entre startOrientation et targetOrientation (vous pouvez trouver cette fonction dans common/quaternion_utils.cpp.).

VII-G. Comment utiliser LookAt mais en limitant la rotation à une certaine vitesse ?

L'idée de base est d'effectuer un SLERP (utilisez glm::mix), mais de jouer avec la valeur d'interpolation afin que l'angle ne soit pas supérieur à la valeur désirée :

Sélectionnez
float mixFactor = maxAllowedAngle / angleBetweenQuaternions;
quat result = glm::gtc::quaternion::mix(q1, q2, mixFactor);

Voici une fonction plus complexe, qui gère les cas spéciaux. Elle n'utilise même pas directement mix() comme optimisation :

Sélectionnez
quat RotateTowards(quat q1, quat q2, float maxAngle){

    if( maxAngle < 0.001f ){
        // Aucune rotation permise. Évite une division par 0 plus tard.
        return q1;
    }

    float cosTheta = dot(q1, q2);

    // q1 et q2 sont déjà égaux.
    // Force q2 just to be sure
    if(cosTheta > 0.9999f){
        return q2;
    }

    // Évite de prendre le long chemin autour de la sphère
    if (cosTheta < 0){
        q1 = q1*-1.0f;
        cosTheta *= -1.0f;
    }

    float angle = acos(cosTheta);

    // S'il y a seulement deux degrés de différence et que l'on en permet cinq
    // alors on a fini.
    if (angle < maxAngle){
        return q2;
    }

    float fT = maxAngle / angle;
    angle = maxAngle;

    quat res = (sin((1.0f - fT) * angle) * q1 + sin(fT * angle) * q2) / sin(angle);
    res = normalize(res);
    return res;

}

Que vous pouvez utiliser comme suit :

Sélectionnez
CurrentOrientation = RotateTowards(CurrentOrientation, TargetOrientation, 3.14f * deltaTime );

(Vous pouvez trouver cette fonction dans common/quaternion_utils.cpp.)


OpenGL Moderne - tutoriel 18 : particules/instanciation

Vu dans ... https://opengl.developpez.com/tutoriels/opengl-tutorial/18-2-particules-instanciation/

https://opengl.developpez.com/tutoriels/opengl-tutorial/18-2-particules-instanciation/

I. Introduction

Les particules sont très similaires aux billboards 3D. Il y a toutefois quelques différences importantes :

  • il y a généralement énormément de particules ;
  • elles se déplacent ;
  • elles apparaissent et meurent ;
  • elles sont semi-transparentes.

Toutes ces différences entraînent des problèmes. Ce tutoriel présente UNE solution pour les résoudre ; il y a d'autres possibilités.

II. Des particules, plein partout !

La première idée pour dessiner une multitude de particules serait d'utiliser le code du tutoriel précédent et d'appeler glDrawArrays pour chaque particule. C'est une très mauvaise idée, car cela signifie que l'ensemble de vos superbes multiprocesseurs GTX 512+ seront tous utilisés pour ne dessiner qu'UN unique rectangle (évidemment, un seul sera utilisé, perdant 99 % d'efficacité). Puis vous allez dessiner le billboard suivant et cela sera identique.

Clairement, on doit trouver une méthode pour dessiner toutes les particules en même temps.

Il y a plusieurs façons pour le faire ; en voici trois :

  • générer un seul VBO avec toutes les particules à l'intérieur. Facile, efficace, fonctionne sur toutes les plates-formes ;
  • utiliser les geometry shaders. Ce n'est pas exploré dans ce tutoriel, notamment parce que 50 % des ordinateurs ne les supportent pas ;
  • utiliser l'instanciation. Ce n'est pas disponible sur TOUTES les plates-formes, mais une grande majorité de celles-ci.

Dans ce tutoriel, nous allons utiliser la troisième option : c'est un bon compromis entre les performances et la disponibilité et par-dessus tout, il est facile d'ajouter le support de la première méthode une fois que celle-ci fonctionne.

II-A. Instanciation

« Instanciation » signifie que l'on a un modèle de base (dans ce cas, un simple rectangle de deux triangles), mais de nombreuses instances de ce rectangle.

Techniquement, cela s'effectue avec plusieurs tampons :

  • certains pour décrire un modèle de base ;
  • d'autres pour décrire les particularités de chaque instance du modèle de base.

Vous avez de nombreuses, très nombreuses options sur ce que vous mettez dans chaque tampon. Dans notre simple cas, on a :

  • un tampon pour les sommets du modèle. Aucun tampon d'indices, donc c'est six vec3, constituant deux triangles, formant un rectangle ;
  • un tampon pour le centre des particules ;
  • un tampon pour la couleur des particules.

Ce sont des tampons standards. Ils sont créés de cette façon :

Sélectionnez
// Le VBO contenant les quatre sommets des particules.
// Grâce à l'instanciation, ils seront partagés par toutes les particules.
static const GLfloat g_vertex_buffer_data[] = {
 -0.5f, -0.5f, 0.0f,
 0.5f, -0.5f, 0.0f,
 -0.5f, 0.5f, 0.0f,
 0.5f, 0.5f, 0.0f,
};
GLuint billboard_vertex_buffer;
glGenBuffers(1, &billboard_vertex_buffer);
glBindBuffer(GL_ARRAY_BUFFER, billboard_vertex_buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(g_vertex_buffer_data), g_vertex_buffer_data, GL_STATIC_DRAW);

// Le VBO contenant la position et la taille des particules
GLuint particles_position_buffer;
glGenBuffers(1, &particles_position_buffer);
glBindBuffer(GL_ARRAY_BUFFER, particles_position_buffer);
// Initialisé avec un tampon vide (NULL) : il sera mis à jour plus tard, à chaque image.
glBufferData(GL_ARRAY_BUFFER, MaxParticles * 4 * sizeof(GLfloat), NULL, GL_STREAM_DRAW);

// Le VBO contenant la couleurs des particules
GLuint particles_color_buffer;
glGenBuffers(1, &particles_color_buffer);
glBindBuffer(GL_ARRAY_BUFFER, particles_color_buffer);
// Initialisé avec un tampon vide (NULL) : il sera mis à jour plus tard, à chaque image.
glBufferData(GL_ARRAY_BUFFER, MaxParticles * 4 * sizeof(GLubyte), NULL, GL_STREAM_DRAW);

Ce qui est habituel. Ils sont mis à jour de cette façon :

Sélectionnez
// Mise à jour des tampons qu'OpenGL utilise pour le rendu.
// Il y a des façons bien plus sophistiquées pour envoyer des données du CPU au GPU,
// mais c'est en dehors du champ de ce tutoriel.
// http://www.opengl.org/wiki/Buffer_Object_Streaming

glBindBuffer(GL_ARRAY_BUFFER, particles_position_buffer);
glBufferData(GL_ARRAY_BUFFER, MaxParticles * 4 * sizeof(GLfloat), NULL, GL_STREAM_DRAW); // Tampon orphelin, une méthode commune pour améliorer les performances de streaming. Voir le lien ci-dessus pour plus de détails.
glBufferSubData(GL_ARRAY_BUFFER, 0, ParticlesCount * sizeof(GLfloat) * 4, g_particule_position_size_data);

glBindBuffer(GL_ARRAY_BUFFER, particles_color_buffer);
glBufferData(GL_ARRAY_BUFFER, MaxParticles * 4 * sizeof(GLubyte), NULL, GL_STREAM_DRAW); // Tampon orphelin, une méthode commune pour améliorer les performances de streaming. Voir le lien ci-dessus pour plus de détails.
glBufferSubData(GL_ARRAY_BUFFER, 0, ParticlesCount * sizeof(GLubyte) * 4, g_particule_color_data);

Ce qui est aussi habituel. Ils sont liés avant le rendu de cette façon :

Sélectionnez
// Premier tampon d'attribut : sommets
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, billboard_vertex_buffer);
glVertexAttribPointer(
 0, // attribut. Aucune raison précise pour 0, mais cela doit correspondre à la disposition dans le shader.
 3, // taille
 GL_FLOAT, // type
 GL_FALSE, // normalisé ?
 0, // nombre d'octets séparant deux sommets dans le tampon
 (void*)0 // décalage du tableau de tampon
);

// Second tampon d'attribut : position et centre des particules
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, particles_position_buffer);
glVertexAttribPointer(
 1, // attribut. Aucune raison précise pour 1, mais cela doit correspondre à la disposition dans le shader.
 4, // taille : x + y + z + taille => 4
 GL_FLOAT, // type
 GL_FALSE, // normalisé ?
 0, // nombre d'octets séparant deux sommets dans le tampon
 (void*)0 // décalage du tableau de tampon
);

// 3e tampon d'attributs : couleurs des particules
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, particles_color_buffer);
glVertexAttribPointer(
 2, // attribut. Aucune raison précise pour 2, mais cela doit correspondre à la disposition dans le shader.
 4, // taille : r + g + b + a => 4
 GL_UNSIGNED_BYTE, // type
 GL_TRUE, // normalisé ? *** OUI, cela signifie que le unsigned char[4] sera accessible avec un vec4 (float) dans le shader ***
 0, // nombre d'octets séparant deux sommets dans le tampon
 (void*)0 // décalage du tableau de tampon
);

Ce qui est normal. La différence vient lors du rendu. Au lieu d'utiliser glDrawArrays (ou glDrawElements si votre modèle de base possède un tampon d'indices), vous utilisez glDrawArraysInstanced/glDrawElementsInstanced, qui est équivalent à appeler N fois glDrawArrays (N est le dernier paramètre, dans notre cas ParticlesCount).

Sélectionnez
glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, ParticlesCount);

Mais il manque quelque chose ici. On n'a pas indiqué à OpenGL quel tampon était pour le modèle de base et lesquels sont pour les différentes instances. Cela se fait avec glVertexAttribDivisor. Voici le code complet commenté :

Sélectionnez
// Ces fonctions sont spécifiques à glDrawArrays*Instanced*.
// Le premier paramètre est le tampon d'attributs dont on parle.
// Le second paramètre est le "débit auquel les sommets d'attributs génériques avance lors du rendu de multiples instances"
// http://www.opengl.org/sdk/docs/man/xhtml/glVertexAttribDivisor.xml
glVertexAttribDivisor(0, 0); // sommets des particules : toujours réutiliser les quatre mêmes sommets -> 0
glVertexAttribDivisor(1, 1); // positions : une par rectangle (son centre) -> 1
glVertexAttribDivisor(2, 1); // couleur : une par rectangle -> 1

// Dessine les particules !
// Cela dessine plusieurs fois un petit triangle_strip (qui ressemble à un rectangle).
// C'est l'équivalent de :
// for(i in ParticlesCount) : glDrawArrays(GL_TRIANGLE_STRIP, 0, 4),
// mais plus rapide.
glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, ParticlesCount);

Comme vous pouvez le voir, l'instanciation est très polyvalente, car vous pouvez passer n'importe quel entier comme AttribDivisor. Par exemple, avec glVertexAttribDivisor(2, 10), chaque groupe de dix instances aura la même couleur.

II-B. Quel est le but ?

Le but est que maintenant, on doit seulement mettre à jour un petit tampon à chaque image (le centre des particules) et non plus un immense modèle. C'est un gain de 75 % pour la bande passante.

III. Vie et mort

Contrairement à la plupart des objets de la scène, les particules meurent et naissent à un grand débit. On doit trouver une méthode convenablement rapide pour obtenir de nouvelles particules et se débarrasser d'elles, quelque chose de mieux que new Particle().

III-A. Créer de nouvelles particules

Pour cela, on doit avoir un grand conteneur de particules :

Sélectionnez
// Représentation CPU d'une particule
struct Particle{
    glm::vec3 pos, speed;
    unsigned char r,g,b,a; // Couleur
    float size, angle, weight;
    float life; // Vie restante pour la particule. Si < 0 : meurt et inutilisée.

};

const int MaxParticles = 100000;
Particle ParticlesContainer[MaxParticles];

Maintenant, on a besoin d'une méthode pour en créer. Cette fonction recherche linéairement dans ParticlesContainer, ce qui est une idée horrible, sauf qu'elle démarre au dernier emplacement connu, donc cette fonction retourne généralement immédiatement :

Sélectionnez
int LastUsedParticle = 0;

// Trouve une Particle dans ParticlesContainer qui n'est actuellement pas utilisée.
// (i.e. life < 0);
int FindUnusedParticle(){

    for(int i=LastUsedParticle; i<MaxParticles; i++){
        if (ParticlesContainer[i].life < 0){
            LastUsedParticle = i;
            return i;
        }
    }

    for(int i=0; i<LastUsedParticle; i++){
        if (ParticlesContainer[i].life < 0){
            LastUsedParticle = i;
            return i;
        }
    }

    return 0; // Toutes les particules sont prises, écrase la première
}

On peut maintenant remplir ParticlesContainer[particleIndex] avec des valeurs intéressantes pour life, color, speed et position. Voir le code pour les détails, mais vous pouvez faire ce que vous voulez ici. La seule chose intéressante est, comment plusieurs particules doivent être générées à chaque image ? C'est principalement dépendant de l'application, donc prenons un taux de 10 000 nouvelles particules par seconde (oui, c'est énorme) :

Sélectionnez
int newparticles = (int)(deltaTime*10000.0);

Sauf que vous devez restreindre cela a une nombre fixe :

Sélectionnez
// Génère dix nouvelles particules chaque milliseconde,
// mais limite cela à 16 ms (60 fps), ou si vous avez une longue image (1 seconde),
// newparticles sera énorme et la prochaine image encore plus longue.
int newparticles = (int)(deltaTime*10000.0);
if (newparticles > (int)(0.016f*10000.0))
    newparticles = (int)(0.016f*10000.0);

III-B. Supprimer les vieilles particules

Il y a une astuce, comme montré ci-dessous Image non disponible.

IV. La boucle principale de simulation

ParticlesContainer contient aussi bien les particules actives que les « mortes », mais le tampon que l'on envoie au GPU ne doit avoir que les particules vivantes.

Donc, on va parcourir chaque particule, vérifier si elle est vivante, si elle doit mourir et si tout est correct, ajouter un peu de gravité, puis finalement la copier dans un tampon GPU spécifique.

Sélectionnez
// Simule toutes les particules
int ParticlesCount = 0;
for(int i=0; i<MaxParticles; i++){

    Particle& p = ParticlesContainer[i]; // raccourci

    if(p.life > 0.0f){

        // Décrémente la vie
        p.life -= delta;
        if (p.life > 0.0f){

            // Simule une physique simple : seulement la gravité, pas de collisions
            p.speed += glm::vec3(0.0f,-9.81f, 0.0f) * (float)delta * 0.5f;
            p.pos += p.speed * (float)delta;
            p.cameradistance = glm::length2( p.pos - CameraPosition );
            //ParticlesContainer[i].pos += glm::vec3(0.0f,10.0f, 0.0f) * (float)delta;

            // Remplit le tampon GPU
            g_particule_position_size_data[4*ParticlesCount+0] = p.pos.x;
            g_particule_position_size_data[4*ParticlesCount+1] = p.pos.y;
            g_particule_position_size_data[4*ParticlesCount+2] = p.pos.z;

            g_particule_position_size_data[4*ParticlesCount+3] = p.size;

            g_particule_color_data[4*ParticlesCount+0] = p.r;
            g_particule_color_data[4*ParticlesCount+1] = p.g;
            g_particule_color_data[4*ParticlesCount+2] = p.b;
            g_particule_color_data[4*ParticlesCount+3] = p.a;

        }else{
            // Les particules qui viennent de mourir seront placé à la fin du tampon dans SortParticles();
            p.cameradistance = -1.0f;
        }

        ParticlesCount++;

    }
}

Voici ce que vous obtenez. Presque réussi, mais il y a un souci…

Problème avec les particules

IV-A. Tri

Comme expliqué dans le dixième tutoriel, vous devez trier les objets semi-transparents du plus loin au plus proche pour que le mélange soit correct.

Sélectionnez
void SortParticles(){
    std::sort(&ParticlesContainer[0], &ParticlesContainer[MaxParticles]);
}

Maintenant, std::sort nécessite une fonction qui peut indiquer si une particule doit être placée avant ou après une autre particule dans le conteneur. Cela peut être fait avec Particle::operator< :

Sélectionnez
// CPU representation of a particle
struct Particle{

    ...

    bool operator<(Particle& that){
        // Tri dans l'ordre inverse : les particules les plus lointaines sont dessinées en premier.
        return this->cameradistance > that.cameradistance;
    }
};

Cela permet de trier ParticleContainer et les particules vont maintenant être affichées correctement(1) :

Un joli effet de fontaine avec des particules

V. Aller plus loin

V-A. Particules animées

Vous pouvez animer votre texture de particules avec une texture atlas. Envoyez l'âge de chaque particule avec la position et dans les shaders, calculez les coordonnées UV comme on l'a fait dans le tutoriel de police 2D. Une texture atlas ressemble à cela :

Exemple de texture atlas

V-B. Gérer plusieurs systèmes de particules

Si vous avez besoin de plus d'un système de particules, vous avez deux options : soit utiliser un seul ParticleContainer, soit un par système.

Si vous avez un seul conteneur pour toutes les particules, alors vous allez être capable de parfaitement les trier. Le principal inconvénient est que vous devez utiliser la même texture pour toutes les particules, ce qui est un gros problème. Cela peut être corrigé en utilisant une texture atlas (une grande texture avec toutes les différentes textures sur celle-ci, ayant simplement des coordonnées UV différentes), mais ce n'est pas vraiment pratique pour l'éditer et l'utiliser.

Si vous avez un conteneur par système de particules, d'un autre côté, les particules seront seulement triées dans ces conteneurs : si deux systèmes de particules se superposent, des artefacts apparaîtront. Suivant votre application, cela peut ne pas être un souci.

Bien sûr, vous pouvez aussi utiliser un quelconque système hybride avec plusieurs systèmes de particules et chacun avec un (petit et gérable) atlas.

V-C. Particules douces

Vous allez remarquer très rapidement un artefact classique :lorsque vos particules croisent une géométrie, la limite devient très visible et moche :

Problème avec les particules et les géométries

(image provenant de http://www.gamerendering.com/2009/09/16/soft-particles/).

Une technique classique pour corriger cela est de tester si le fragment actuellement dessiné est proche du tampon de profondeur. Si c'est le cas, le fragment est fondu.

Par contre, vous allez devoir échantillonner le tampon de profondeur, ce qui n'est pas possible avec le tampon de profondeur « normal ». Vous devez afficher votre scène dans une cible de rendu. Sinon, vous pouvez copier le tampon de profondeur à partir d'un tampon d'image vers un autre avec glBlitFramebuffer.

http://developer.download.nvidia.com/whitepapers/2007/SDK10/SoftParticles_hi.pdf

V-D. Amélioration du taux de remplissage

L'un des facteurs les plus limitants dans les GPU modernes est le taux de remplissage : le nombre de fragments (pixels) que le GPU peut écrire en 16,6 ms, permis pour obtenir 60 FPS.

C'est un problème, car les particules nécessitent typiquement un GRAND taux de remplissage, comme vous devez redessiner le même fragment dix fois, chaque fois avec une particule spécifique ; et si vous ne le faites pas, vous allez obtenir les mêmes artefacts que précédemment.

Parmi tous les fragments qui sont écrits, la plupart sont complètement inutiles : ceux se trouvant sur la bordure. Vos textures de particules sont souvent complètement transparentes sur les bordures, mais le modèle de particule continuera de les dessiner - et mettre à jour le tampon de couleur avec exactement la même valeur que précédemment.

Ce petit utilitaire calcule un modèle (celui que vous êtes supposé dessiner avec glDrawArrayInstanced()), qui correspond précisément à votre texture :

http://www.humus.name/index.php?page=Cool&ID=8. Le site de Emil Person contient plein d'autres articles fascinant.

V-E. Physique des particules

Au bout d'un moment, vous allez probablement souhaiter que vos particules interagissent bien plus avec votre monde. En particulier, les particules pourraient rebondir sur le sol.

Vous pouvez simplement lancer un rayon pour chaque particule, entre la position actuelle et la prochaine ; on apprend à le faire dans les tutoriels de Picking. Mais c'est très coûteux. simplement, vous ne pouvez pas le faire pour chaque particule, à chaque image.

Suivant votre application, vous pouvez soit approcher votre géométrie avec un ensemble de plans et faire un lancer de rayon sur ces plans uniquement ; ou vous pouvez utiliser un vrai lancer de rayon, mais mettre en cache le résultat et approcher les collisions alentour avec le cache (ou vous pouvez faire les deux).

Une technique complètement différente est d'utiliser le tampon de profondeur existant tel une approximation très brute de la géométrie (visible) et faire collisionner les particules avec celle- ci. C'est « assez efficace » et rapide, mais vous allez devoir effectuer toute la simulation sur le GPU, car vous ne pouvez pas accéder au tampon de profondeur à partir du CPU (du moins, pas rapidement), donc c'est bien plus compliqué.

Voici quelques liens sur ces techniques :

  • http://www.altdevblogaday.com/2012/06/19/hack-day-report/ ;
  • http://www.gdcvault.com/search.php#&category=free&firstfocus=&keyword=Chris+Tchou%27s%2BHalo%2BReach%2BEffects&conference_id=.

V-F. Simulation GPU

Comme indiqué précédemment, vous pouvez complètement simuler le mouvement des particules sur le GPU. Vous allez toujours devoir gérer le cycle de vie de vos particules sur le CPU - au moins pour les faire apparaître.

Vous avez plusieurs options pour ce faire et aucune ne rentre dans le champ de ce tutoriel ; je vais juste donner quelques pointeurs.

  • Utiliser le retour de transformation (Transform Feedback). Cela vous permet de stocker les sorties du vertex shader dans un VBO côté GPU. Stockez la nouvelle position dans ce VBO et à la prochaine image, utilisez ce VBO comme point de départ et stockez les nouvelles positions dans le premier VBO.
  • Même chose, mais sans le retour de transformation : encodez vos positions de particules dans une texture et mettez-la à jour avec un rendu vers texture.
  • Utiliser une bibliothèque GPU généraliste : CUDA ou OpenCL, qui possède des fonctions d'interopérabilité avec OpenGL.
  • Utilisez un compute shader. La solution la plus propre, mais seulement disponible sur les GPU récents.


OpenGL Moderne - tutoriel 18-1 : billboards

Vu dans ... https://opengl.developpez.com/tutoriels/opengl-tutorial/18-1-billboards/

billboards

I. Introduction

Les billboards sont des éléments 2D incrustés dans un monde 3D. Ce n'est pas un menu 2D au-dessus de tout le reste ni un plan 3D autour duquel vous pouvez tourner, mais quelque chose entre les deux, comme les barres de vies dans beaucoup de jeux.

Ce qui diffère avec les billboards est qu'ils sont positionnés à un endroit spécifique, mais que leur orientation est automatiquement calculée afin qu'ils soient toujours face à la caméra.

II. Solution n° 1 : la méthode 2D

Cette méthode est très simple.

Calculez simplement où votre point est à l'écran et affichez un texte 2D (voir le onzième tutoriel) à cette position.

Sélectionnez
// Tout ce qu'il y a ici est expliqué dans le troisième tutoriel ! Il n'y rien de nouveau.
glm::vec4 BillboardPos_worldspace(x,y,z, 1.0f);
glm::vec4 BillboardPos_screenspace = ProjectionMatrix * ViewMatrix * BillboardPos_worldspace;
BillboardPos_screenspace /= BillboardPos_screenspace.w;

if (BillboardPos_screenspace.z < 0.0f){
    // L'objet est derrière la caméra, ne l'affichez pas.
}

Voilà !

En plus d'être super facile à implémenter, le billboard aura la même taille, peu importe sa distance avec la caméra. Mais le texte 2D est toujours affiché au-dessus de tout le reste et cela peut désorganiser le rendu et s'afficher au-dessus d'autres objets.

III. Solution n° 2 : la méthode 3D

Celle-ci est habituellement mieux et pas trop compliquée.

Le but est de garder le modèle aligné avec la caméra, mais si la caméra se déplace :

Seconde méthode de billboards, le modèle est aligné à la caméra

Vous pouvez voir ce problème comme étant un souci de génération de matrice de modèle, même si ce n'est pas aussi simple que cela.

L'idée est que chaque coin du billboard est au centre, déplacé par les vecteurs haut et droit de la caméra ;

Schéma fonctionnement billboard

Bien sûr, on ne connaît que le centre du billboard dans l'espace monde, dans lequel nous avons aussi besoin des vecteurs haut/droit de la caméra.

Dans l'espace caméra, le vecteur haut de celle-ci est (0, 1, 0). Pour l'obtenir dans l'espace monde, multipliez-le par la matrice qui transforme de l'espace caméra vers l'espace monde, qui est, bien sûr, l'inverse de la matrice de vue.

Une façon plus simple de l'exprimer en mathématique est :

Sélectionnez
CameraRight_worldspace = {ViewMatrix[0][0], ViewMatrix[1][0], ViewMatrix[2][0]}
CameraUp_worldspace = {ViewMatrix[0][1], ViewMatrix[1][1], ViewMatrix[2][1]}

Une fois que l'on a cela, il est très simple de calculer la position finale du sommet :

Sélectionnez
vec3 vertexPosition_worldspace =
    particleCenter_wordspace
    + CameraRight_worldspace * squareVertices.x * BillboardSize.x
    + CameraUp_worldspace * squareVertices.y * BillboardSize.y;
  • particleCenter_worldspace est, comme son nom l'indique, la position du centre du billboard : elle est déterminée en utilisant une variable uniforme vec3 ;
  • squareVertices est le modèle original. SquareVertices.x est -0.5 pour les sommets de gauche, qui sont donc déplacé vers la gauche de la caméra (à cause du *CameraRight_worldsspace) ;
  • BillboardSize est la taille du billboard, en unités monde, envoyée avec une variable uniforme.

Et presto, voici le résultat. C'était facile, non ?

Billboard aligné à la caméra

Pour information, voici comment squareVertices est définie :

Sélectionnez
// Le VBO contient les quatre sommets des particules.
 static const GLfloat g_vertex_buffer_data[] = {
 -0.5f, -0.5f, 0.0f,
 0.5f, -0.5f, 0.0f,
 -0.5f, 0.5f, 0.0f,
 0.5f, 0.5f, 0.0f,
 };

IV. Solution n° 3 : la méthode 3D avec taille fixe

Comme vous pouvez le voir ci-dessus, la taille du billboard change suivant la distance de la caméra. Dans certains cas, cela est le résultat attendu, mais dans d'autres, tel que les barres de vies, vous souhaitez probablement une taille fixe à la place.

Comme le déplacement entre le centre et un coin doit être fixe dans l'espace écran, c'est exactement ce que l'on va faire : calculer la position du centre dans l'espace écran et la décaler.

Sélectionnez
vertexPosition_worldspace = particleCenter_wordspace;
// Obtenir la position dans l'espace écran du centre de la particule
gl_Position = VP * vec4(vertexPosition_worldspace, 1.0f);
// Ici on doit faire la division de perspective soi-même.
gl_Position /= gl_Position.w;

// Déplace les sommets directement dans l'espace écran. Pas besoin de CameraUp/Right_worlspace ici.
gl_Position.xy += squareVertices.xy * vec2(0.2, 0.05);

Souvenez-vous qu'à cet endroit du pipeline de rendu, vous êtes en coordonnées normalisées du périphérique, donc entre -1 et 1 sur les deux axes : ce n'est pas en pixels.

Si vous souhaitez une taille en pixels, c'est facile : utilisez simplement (ScreenSizeInPixels / BillboardSizeInPixels) au lieu de BillboardSizeInScreenPercentage.

Billbpoard avec une taille fixe

V. Solution n° 4 : rotation verticale seule

Quelques systèmes représentent les arbres et les lampes lointains tels des billboards. Mais vous ne souhaitez vraiment, vraiment pas, que votre arbre soit tordu : il DOIT être vertical. Donc vous avez besoin d'un système hybride qui tourne seulement autour d'un seul axe.

Bien, ce cas-là est laissé comme exercice pour le lecteur !


OpenGL Moderne - Annexe D : compiler vos propres applications C

Vu dans ... https://opengl.developpez.com/tutoriels/opengl-tutorial/annexe-D-compiler-vos-propre-applications-C/

Annexe D : compiler vos propres applications C

I. Introduction

De nombreux efforts ont été effectués pour que ces tutoriels soient faciles à compiler et exécuter. Malheureusement, cela signifie aussi que CMake cache la façon de faire pour votre propre projet.

Donc, ce tutoriel va expliquer comment compiler votre propre application C à partir de rien. Mais avant tout, vous avez besoin de quelques connaissances de base sur ce que votre compilateur fait.

Veuillez ne pas sauter les deux premières sections. Si vous lisez ce tutoriel, vous avez probablement besoin de connaître ces choses.

II. Le modèle des applications C

II-A. Le préprocesseur

On parle ici des #define et #include.

L'étape du préprocesseur est très simple : du copier-coller.

Lorsque le préprocesseur voit le code du fichier MyCode.c suivant :

Sélectionnez
#include "MyHeader.h"

void main(){
    FunctionDefinedInHeader();
}

Il ouvre simplement le fichier MyHeader.h et copie-colle son contenu dans le fichier MyCode.c.

Sélectionnez
// Début de MyCode.c
// Début de MyHeader.h
#ifndef MYHEADER_H
#define MYHEADER_H

void FunctionDefinedInHeader(); // Déclare la fonction

#endif
// Fin de MyHeader.h

void main(){
    FunctionDefinedInHeader(); // Utilisation
}

// Fin de MyCode.c

De façon similaire, les #define sont copiées-collées, les #if sont analysés et potentiellement retirés, etc.

À la fin de cette étape, vous obtenez un fichier C++, sans #define, #if, #ifdef, #include, prêt à être compilé.

Par exemple, voici le fichier main.cpp du sixième tutoriel, complètement prétraité dans Visual : fichier prétraité. Attention, c'est un immense fichier ! Mais cela vaut le coup de savoir ce qu'un fichier .cpp simple est réellement pour le compilateur.

II-B. Compilation

Le compilateur traduit le code C++ en une représentation que le CPU peut comprendre directement. Par exemple, le code suivant :

Sélectionnez
int i=3;
int j=4*i+2;

sera traduit en ces opcodes x86 :

Sélectionnez
mov         dword ptr [i],3
mov         eax,dword ptr [i]
lea         ecx,[eax*4+2]
mov         dword ptr [j],ecx

Chaque fichier .cpp est compilé séparément et le code binaire résultant est écrit dans des fichiers .o/.obj.

Processus de compilation

Nous n'avons pas encore l'exécutable : il reste une étape à faire.

II-C. Éditeur de liens

L'éditeur de liens prend tout le code binaire (les vôtres, mais aussi celui des bibliothèques externes) et génère l'exécutable final. Quelques notes :

  • Une bibliothèque possède l'extension .lib.
  • Certaines bibliothèques sont statiques. Cela signifie que les .lib contiennent tous les opcodes x86 nécessaires.
  • Certaines bibliothèques sont dynamiques (aussi appelées « bibliothèques partagées »). Cela signifie que le fichier .lib ne contient pas tout le code x86 ; il indique simplement : « Je jure que la fonction Foo, Bar et WhatsNot seront disponibles à l'exécution ».

Lorsque l'éditeur de liens a été exécuté, vous obtenez un exécutable (.exe sous Windows, sans extension sous Unix) :

Image non disponible

II-D. Exécution

Lorsque vous lancez l'exécutable, le système d'exploitation va ouvrir le .exe et placer les opcodes x86 en mémoire. Comme indiqué précédemment, certains codes ne sont pas disponibles à ce moment-là : le code des bibliothèques dynamiques. Mais l'éditeur de liens a été assez gentil pour indiquer où les trouver : le .exe indique clairement que la fonction glClearColor est implémentée dans le fichier OpenGL32.dll.

Exemple du produit de l'éditeur de liens

Windows va ouvrir la .dll et trouver glClearColor :

Fonctions dans Dependency Walker

Quelques fois, une .dll ne peut être trouvée, sûrement, car l'installation a échoué et le programme ne peut pas s'exécuter.

Dépendance EXE DLL

III. Comment faire X avec l'éditeur Y ?

Les instructions de compilation d'une application OpenGL sont séparées des opérations de base suivantes. En voici les raisons :

  • premièrement, vous devez faire ce genre de chose tout le temps, donc vous avez intérêt à les connaître correctement ;
  • deuxièmement, vous allez connaître ce qui est spécifique à OpenGL de ce qui ne l'est pas.

III-A. Visual Studio

III-A-1. Créer un nouveau projet

Fichier (File) → Nouveau (New) → Projet (Project) → Projet vide (Empty Project). N'utilisez pas ces étranges assistants. N'utilisez aucune option que vous ne connaissez pas (désactivez MFC, ATL, les entêtes précompilés, stdafx, le fichier main).

III-A-2. Ajouter un fichier source dans un projet

Faites un clic droit sur Fichiers sources (Sources Files) → Ajouter nouveau (Add new).

III-A-3. Ajouter un répertoire d'inclusion

Faites un clic droit sur projet (Project) → Propriétés du projet (Project Properties) → C++ → Général → Répertoire d'inclusion additionnel (Additional include directories). C'est une liste déroulante, vous pouvez la modifier comme vous le souhaitez.

III-A-4. Lier une bibliothèque

Faites un clic droit sur projet (Project) → Propriétés du projet (Project Properties) → Éditeur de liens (Linker) → Entrée (Input) → Dépendances additionnelles (Additional dependencies) : tapez le nom du fichier .lib. Par exemple : opengl32.lib.

Dans les propriétés du projet (Project Properties) → Éditeur de liens (Linker) → Général → répertoires de bibliothèques additionnels (Additional library directories), assurez-vous que le chemin de la bibliothèque ci-dessus est présent.

III-A-5. Compiler, exécuter et déboguer

Pour définir le répertoire de travail (là où sont les textures et les shaders) : Propriétés du projet (Project Properties) → Débogage (Debugging) → Répertoire de travail (Working Directory).

Pour exécuter : Maj-F5 ; mais vous ne souhaitez probablement jamais faire cela. Pour déboguer : F5.

Voici une courte liste de raccourcis pour le débogage :

  • F9 sur une ligne, ou en faisant un clic gauche sur les numéros de ligne : définit un point d'arrêt. Un point rouge va apparaître ;
  • F10 : exécuter la ligne actuelle ;
  • F11 : exécuter la ligne actuelle, mais en s'enfoncer dans les fonctions que cette ligne appelle (« Step into ») ;
  • Maj-F1 : exécuter jusqu'à la fin de la fonction (« Step out »).

Vous avez aussi de nombreuses fenêtres de débogage : les variables observées, la pile d'appels, les threads…

III-B. QtCreator

QtCreator est disponible gratuitement à cette adresse.

III-B-1. Créer un nouveau projet

Utilisez un projet vide C ou C++ ; éviter les templates contenant des trucs de Qt.

Nouveau projet dans Qt Creator

Utilisez les options par défaut.

III-B-2. Ajouter un fichier source dans un projet

Utilisez l'interface, ou ajoutez un fichier dans le .pro :

Sélectionnez
SOURCES += main.cpp \
           other.cpp \
           foo.cpp

III-B-3. Ajouter un répertoire d'inclusion

Dans le fichier .pro :

Sélectionnez
INCLUDEPATH += <chemin> \ <autre chemin>

III-B-4. Lier avec une bibliothèque

Faites un clic droit sur le projet → Ajout une bibliothèque (Add library)

  • Si vous êtes sous Linux et que vous avez installé la bibliothèque à partir de apt-get ou similaire, il y a de fortes chances que la bibliothèque soit enregistrée dans le système. Vous pouvez sélectionner « Paquet système » (System package) et entrer le nom de la bibliothèque (par exemple : libglfw ou glew).
    Image non disponible

  • Sinon, utilisez « Bibliothèque système » (System library). Naviguez jusqu'à atteindre le dossier où vous l'avez compilée.

III-B-5. Compiler, exécuter et déboguer

Pour compiler : Ctrl-B ou le marteau dans le coin inférieur gauche.

Pour exécuter : la flèche verte. Vous pouvez définir les arguments du programme et le répertoire de travail dans « Projets » (Projects) → « Paramètres d'exécution » (Run Settings).

Pour déboguer :

  • définir un point d'arrêt : cliquez à gauche des numéros de ligne. Un point rouge apparaîtra ;
  • F10 : exécuter la ligne actuelle ;
  • F11 : exécuter la ligne actuelle, mais en s'enfonçant dans les fonctions que cette ligne appelle (« Step into ») ;
  • Maj-F11 : exécuter jusqu'à la fin de la fonction (« Step out »).

Vous avez aussi de nombreuses fenêtres de débogage : les variables observées, la pile d'appels, les threads…

III-C. CMake

CMake créera les projets et cela pour pratiquement tous les outils de compilation : Visual, Qt Creator, XCode, make, Code::Blocks, Eclipse, etc., sur n'importe quel système d'exploitation. Cela vous libère de la maintenance de multiples fichiers de projet.

III-C-1. Créer un nouveau projet

Créez un fichier CMakeLists.txt et écrivez ceci (adaptez si besoin) :

Sélectionnez
cmake_minimum_required (VERSION 2.6)
project (le_nom_du_projet)

find_package(OpenGL REQUIRED)

add_executable(le_nom_de_l_executable
    tutorial04_colored_cube/tutorial04.cpp
    common/shader.cpp
    common/shader.hpp
)

Lancez l'interface graphique de CMake, naviguez jusqu'à atteindre votre fichier .txt et sélectionnez votre dossier de compilation. Cliquez sur « Configurer » (Configure) puis « Generer » (Generate). Votre solution va être créée dans le dossier de compilation.

III-C-2. Ajouter un fichier source dans un projet

Ajoutez simplement une ligne dans la commande add_executable.

III-C-3. Ajouter des répertoires d'inclusion

Sélectionnez
include_directories(
    external/AntTweakBar-1.15/include/
    external/glfw-2.7.2/include/
    external/glm-0.9.1/
    external/glew-1.5.8/include/
    .
)

III-C-4. Lier une bibliothèque

Sélectionnez
set(ALL_LIBS
    ${OPENGL_LIBRARY}
    GLFW_272
    GLEW_158
    ANTTWEAKBAR_151_OGLCORE_GLFW
)

target_link_libraries(tutorial01_first_window
    ${ALL_LIBS}
)

III-C-5. Compiler, exécuter et déboguer

CMake ne fait pas cela. Utilisez votre EDI favori.

III-D. Make

Veuillez, s'il vous plaît, ne pas utiliser cela.

III-E. GCC

Cela peut être intéressant de compiler un petit projet « à la main » afin de mieux comprendre le flux de travail. Ne le faites pas sur un vrai projet…

Vous pouvez aussi le faire sur Windows avec MinGW.

Compilez chaque fichier .cpp séparément :

g++ -c main.cpp
g++ -c tools.cpp

Comme indiqué précédemment, vous allez obtenir des fichiers main.o et tools.o. Liez-les :

g++ main.o tools.o

un fichier a.out apparaît ; c'est votre exécutable. Exécutez-le :

./a.out

C'est tout !

IV. Compiler votre propre application C

Armé de cette connaissance, on peut commencer à compiler soi-même une application OpenGL.

  1. Téléchargez les dépendances : ici, on utilise GLFW, GLEW et GLM, mais suivant votre projet, vous pouvez avoir besoin d'autre chose. Sauvegardez-les préférablement dans des sous-répertoires de votre projet (par exemple : external/).
  2. Elles doivent être précompilées pour votre plateforme. Toutefois, GLM n'a pas besoin d'être compilée.
  3. Créez un nouveau projet avec l'EDI de votre choix.
  4. Ajoutez un nouveau fichier .cpp dans le projet.

  5. Copiez et collez, par exemple, le code suivant (c'est en fait le fichier playground.cpp) :

    Sélectionnez
    #include <stdio.h>
#include <stdlib.h>

#include <GL/glew.h>

#include <GL/glfw.h>

#include <glm/glm.hpp>
using namespace glm;

int main( void )
{
    // Initialise GLFW
    if( !glfwInit() )
    {
        fprintf( stderr, "Failed to initialize GLFW\n" );
        return -1;
    }

    glfwOpenWindowHint(GLFW_FSAA_SAMPLES, 4);
    glfwOpenWindowHint(GLFW_WINDOW_NO_RESIZE,GL_TRUE);
    glfwOpenWindowHint(GLFW_OPENGL_VERSION_MAJOR, 3);
    glfwOpenWindowHint(GLFW_OPENGL_VERSION_MINOR, 3);
    glfwOpenWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    // Ouvre une fenêtre et créé son contexte OpenGL
    if( !glfwOpenWindow( 1024, 768, 0,0,0,0, 32,0, GLFW_WINDOW ) )
    {
        fprintf( stderr, "Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials.\n" );
        glfwTerminate();
        return -1;
    }

    // Initialise GLEW
    if (glewInit() != GLEW_OK) {
        fprintf(stderr, "Failed to initialize GLEW\n");
        return -1;
    }

    glfwSetWindowTitle( "Playground" );

    // Assure que l'on capture la touche Échap lorsque pressée
    glfwEnable( GLFW_STICKY_KEYS );

    // Fond bleu foncé
    glClearColor(0.0f, 0.0f, 0.3f, 0.0f);

    do{
        // Ne dessine rien, à bientôt pour le second tutoriel !

        // Alterne les tampons
        glfwSwapBuffers();

    } // Vérifie si la touche Échap a été pressée ou si la fenêtre a été fermée
    while( glfwGetKey( GLFW_KEY_ESC ) != GLFW_PRESS &&
           glfwGetWindowParam( GLFW_OPENED ) );

    // Ferme la fenêtre OpenGL et termine GLFW
    glfwTerminate();

    return 0;
}
  6. Compilez le projet.

Vous allez obtenir de nombreuses erreurs de compilation. On va les analyser une par une.

V. Dépannage

Les messages d'erreurs ci-dessous sont pour Visual Studio 2010, mais ils sont plus ou moins proches de ceux de GCC.

V-A. Visual Studio - fatal error C1083 : impossible d'ouvrir le fichier 'GL/glew.h' : Aucun fichier trouvé.

(Ou n'importe quel autre fichier.)

Certains entêtes ont des emplacements bizarres. Par exemple, les fichiers à inclure de GLEW sont situés dans external/glew-x.y.z/include. Le compilateur n'a aucune méthode magique pour le deviner, donc vous devez les lui indiquer. Dans les propriétés du projet, ajoutez le chemin approprié dans les options du compilateur (et non pas l'éditeur de liens).

Pour aucune raison vous ne devriez copier les fichiers dans les répertoires du compilateur (Program Files/Visual Studio/…). Techniquement, cela fonctionnera, mais c'est une très mauvaise pratique.

De plus, c'est une bonne chose d'utiliser des chemins relatifs (./external/glew/… au lieu de C:/Users/utilisateur/Downloads/…).

Par exemple, voici ce que contiennent les fichiers de CMake de ces tutoriels :

Sélectionnez
external/glfw-2.7.2/include
external/glm-0.9.1
external/glew-1.5.8/include

Répétez cela jusqu'à ce que tous les fichiers soient trouvés.

V-B. GCC - fatal error : GL/glew.h : aucun fichier trouvé

(ou n'importe quel autre fichier.)

Cela signifie que la bibliothèque n'est pas installée. Si vous êtes chanceux, la bibliothèque est connue et vous n'avez qu'à l'installer. C'est le cas pour GLFW, GLEW et GLM :

Sélectionnez
sudo apt-get install libglfw-dev libglm-dev libglew1.6-dev

Si ce n'est pas une bibliothèque répandue, regardez la réponse pour Visual Studio ci-dessus.

V-C. Visual Studio - error LNK2019 : symbole non résolu glfwGetWindowParam référencé dans la fonction main

(ou n'importe quel autre symbole dans n'importe quelle autre fonction.)

Félicitations ! Vous avez une erreur d'édition de liens. C'est une excellente nouvelle : cela signifie que la compilation a réussi. Plus qu'une dernière étape !

Les fonctions GLFW se trouvent dans une bibliothèque externe. Vous devez indiquer cette bibliothèque à l'éditeur de liens. Ajoutez-la dans les options de l'éditeur de liens. N'oubliez pas d'ajouter le chemin menant à cette bibliothèque.

Par exemple, voici ce que le projet Visual Studio utilise. Les noms sont quelque peu inhabituels, car c'est une compilation personnalisée. De plus, GLM ne nécessite pas d'être compilée ou liée, donc elle n'est pas là.

Sélectionnez
external\Debug\GLFW_272.lib
external\Debug\GLEW_158.lib

Si vous téléchargez ces bibliothèques à partir de SourceForge (GLFW, GLEW) et les compilez vous-même, vous devez spécifier le chemin adéquat. Par exemple :

Sélectionnez
C:\Ou\Vous\Avez\Place\La\Bibliotheque\glfw.lib
C:\Ou\Vous\Avez\Place\La\Bibliotheque\glew32.lib

V-D. GCC - main.cpp : référence indéfinie vers 'glfwInit'

(ou n'importe quel autre symbole.)

Même réponse que pour Visual Studio ci-dessus.

Sur Linux, GLFW et GLEW (et plein d'autres) sont généralement installées avec apt-get ou autre : sudo apt-get install libglew-dev libglfw-dev (cela peut changer). Lorsque vous faites ceci, les bibliothèques sont copiées dans les répertoires standards du compilateur, donc vous n'avez pas à préciser le chemin. Liez simplement glfw et glew comme présenté dans la première section.

V-E. J'ai tout configuré correctement, mais j'ai toujours une erreur « unresolved external error » !

Cela peut être quelque peu difficile à tracer. Il y a plusieurs solutions :

V-E-1. J'ai une erreur durant l'édition des liens avec _imp_glewInit ou d'autres symboles qui commencent par _imp

Cela signifie que la bibliothèque (dans ce cas, glew) a été compilée comme bibliothèque statique, mais vous essayez de l'utiliser comme bibliothèque dynamique. Ajoutez simplement la définition pour le préprocesseur suivante dans les options du compilateur (pour votre propre projet, et non celui de glew) :

Sélectionnez
GLEW_STATIC

V-E-2. J'ai quelques problèmes étranges avec GLFW

Peut-être, car GLFW a été compilée comme bibliothèque dynamique, mais vous essayez de l'utiliser comme bibliothèque statique ?

Essayez d'ajouter la définition pour le préprocesseur suivante :

Sélectionnez
GLFW_DLL

V-E-3. J'ai un autre problème avec l'éditeur de liens ! Aidez-moi, je suis bloqué !

Veuillez envoyer un rapport détaillé et un projet complet compressé et on ajoutera des indications.

V-E-4. J'aimerais résoudre cela moi-même. Quelles sont les règles générales ?

Admettons que vous êtes l'auteur de la GLFW. Vous souhaitez fournir la fonction glfwInit().

Lors de la compilation comme DLL, vous devez indiquer au compilateur que glfwInit() n'est pas comme n'importe quelle autre fonction dans la DLL ; elle doit être vue par les autres, contrairement à glfwPrivateImplementationMethodNobodyShouldCareAbout(). Cela est fait en déclarant la fonction externe « external » avec GCC, ou « __declspec(dllexport) » avec Visual.

Lorsque vous souhaitez utiliser GLFW, vous devez indiquer au compilateur que la fonction n'est pas réellement disponible : il doit la lier dynamiquement. Cela est fait en déclarant la fonction externe « external » avec GCC ou « __declspec(dllimport) » avec Visual.

Donc vous utilisez une définition pratique #define : GLFWAPI et vous l'utilisez pour déclarer les fonctions :

GLFWAPI int glfwInit(void) ;

  • lorsque vous compilez la bibliothèque en DLL, vous définissez #define GLFW_BUILD_DLL. GLFWAPI sera alors défini en __declspec(dllexport) ;
  • lorsque vous utilisez GLFW à travers une DLL, vous définissez #define GLFW_DLL. GLFWAPI sera alors défini en __declspec(dllimport) ;
  • lorsque vous compilez la bibliothèque statique, GLFWAPI est défini vide.
  • Lorsque vous utilisez GLFW en bibliothèque statique, GLFWAPI est défini vide.

Donc la règle est : ces indications doivent être consistantes. Si vous compilez une bibliothèque (n'importe quelle bibliothèque, pas que GLFW) en DLL, utilisez la bonne définition pour le préprocesseur : GLFW_DLL, GLEW_STATIC.

V-F. Mon programme crashe !

Il y a de multiples raisons qu'une application C++ OpenGL plante. Voici quelques-unes d'entre elles. Si vous ne connaissez pas la ligne exacte où votre programme plante, apprenez à utiliser un débogueur (voir les raccourcis ci-dessus). Veuillez NE PAS déboguer avec printf().

V-F-1. Je n'atteins même pas le main

Cela est certainement dû à des DLL qui n'ont pas pu être trouvées. Essayez d'ouvrir votre application avec Dependency Walker (Windows) ou ldd (Linux ; essayez aussi ceci).

V-F-2. Mon programme crashe sur glfwOpenWindow(), ou toute autre fonction qui crée un contexte OpenGL

Plusieurs raisons existent :

  • votre GPU ne supporte pas la version OpenGL demandée. Essayez de voir la version supportée avec GPU Caps Viewer ou un outil similaire. Mettez à jour vos pilotes si la version semble trop faible. Les cartes intégrées Intel sur les netbooks sont particulièrement nulles. Utilisez une version d'OpenGL plus basse (2.1 par exemple) et utilisez les extensions si vous manquez de fonctionnalités ;
  • votre système d'exploitation ne supporte pas la version OpenGL demandée : Mac OS… même réponse ;
  • vous essayez d'utiliser GLEW avec un contexte OpenGL core (c'est-à-dire, sans les choses dépréciées). C'est un bogue de GLEW. Utilisez glewExperimental=true avec glewInit() ou utilisez le mode de compatibilité (c'est-à-dire utilisez GLFW_OPENGL_COMPAT_PROFILE à la place de GLFW_OPENGL_CORE_PROFILE).

V-G. Mon programme crashe sur le premier appel OpenGL, ou sur la première création de tampon

Trois raisons sont possibles :

  • vous n'avez pas appelé glewInit() APRÈS glfwOpenWindow() ;

  • vous utilisez un profil OpenGL core et vous n'avez pas créé de VAO. Ajoutez le code suivant après glewInit() :

    Sélectionnez
    GLuint VertexArrayID;
glGenVertexArrays(1, &VertexArrayID);
glBindVertexArray(VertexArrayID);

  • Vous utilisez la version de GLEW par défaut, qui contient un bogue. Vous ne pouvez utiliser un profil OpenGL core à cause de celui-ci. Utilisez soit glewExperimental=true

  • avec glewInit(), ou demandez à GLFW d'utiliser un profil de compatibilité à la place :
Sélectionnez
glfwOpenWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE);

V-H. Mon programme crashe lorsque j'essaie de charger un fichier

Définissez votre répertoire de travail correctement. Lisez le premier tutoriel.

Créez un fichier test.txt et essayez le code suivant :

Sélectionnez
if ( fopen("test.txt", "r" ) == NULL ){
    printf("I'm probably running my program from a wrong folder");
}

UTILISEZ LE DÉBOGUEUR !

Sincèrement ! Ne déboguez pas avec printf() ; utilisez un bon EDI. http://www.dotnetperls.com/debugging est pour le C# mais est aussi valide pour le C++. Cela peut être différent pour XCode ou Qt Creator, mais les concepts restent exactement les mêmes.


OpenGL Moderne - Annexe E : cliquer sur un objet avec un hack OpenGL

Vu dans ... https://opengl.developpez.com/tutoriels/opengl-tutorial/annexe-E-picking-hack-OpenGL/

Annexe E : cliquer sur un objet avec un hack OpenGL

I. Picking en utilisant un hack OpenGL

Cette technique n'est pas vraiment conseillée, mais c'est une méthode facile et rapide pour ajouter un picking simple. Dans tous les cas, évitez d'utiliser cela dans les jeux, car cela peut provoquer une chute de FPS. Toutefois, si vous avez une simulation et que vous ne vous souciez pas des performances liées au picking, cette technique peut être la solution parfaite.

Le code source pour ce tutoriel est disponible dans le fichier au nom lourd de sens : misc05_picking/misc05_picking_slow_easy.cpp.

I-A. Idée de base

L'idée derrière cette technique est d'afficher la scène, comme d'habitude, mais au lieu d'utiliser un joli rendu, vous dessinez chaque objet avec une couleur spécifique et unique.

Ensuite, vous récupérez la couleur du pixel sous le curseur de la souris et vous utilisez cette couleur pour identifier l'objet. Vous obtenez ainsi l'objet sur lequel l'utilisateur a cliqué.

Voici un exemple :

Scène avec une couleur unique par objet

Dans cette capture d'écran, chaque singe possède une couleur légèrement différente permettant de les identifier.

Bien sûr, vous ne voulez pas voir l'image avec toutes ces couleurs étranges donc vous devez nettoyer l'écran et redessiner comme d'habitude.

II. Implémentation

II-A. Donner un identifiant à chaque objet

Chaque objet de la scène doit avoir un identifiant unique. La façon la plus facile de le faire est de donner à chaque objet un entier et de le convertir en une couleur. Cette couleur ne doit pas avoir de sens ; cette technique est simplement un hack de toute façon.

Dans le code source accompagnant ce tutoriel, 100 objets sont créés et conservés dans un std::vector dont l'identifiant n'est que l'index de l'objet dans ce vecteur. Si vous avez une hiérarchie plus complexe, vous devez certainement ajouter l'identifiant dans votre classe de Mesh et maintenir une sorte de std::map pour associer l'identifiant à l'objet.

II-B. Détecter le clic

Dans cet exemple simple, le picking est effectué à chaque image lorsque le bouton gauche de la souris est appuyé :

Sélectionnez
if (glfwGetMouseButton(GLFW_MOUSE_BUTTON_LEFT)){

Dans une application réelle, vous souhaitez certainement le faire uniquement lorsque l'utilisateur relâche le bouton et donc vous devez garder un booléen estCeQueLeBoutonGaucheAEteAppuyeLorsDeLImagePrecedente, ou mieux, utilisez glfwSetMouseButtonCallback() (lisez le manuel GLFW pour connaître comment l'utiliser).

II-C. Convertir l'identifiant en une couleur spécifique

Comme nous devons afficher chaque modèle avec une couleur différente, la première étape est de calculer cette couleur. Une méthode simple est de placer les bits de poids faible dans le canal rouge et les bits de poids fort dans le canal bleu :

Sélectionnez
// Converti « i », l'identifiant de modèle en une couleur RBG
int r = (i & 0x000000FF) >>  0;
int g = (i & 0x0000FF00) >>  8;
int b = (i & 0x00FF0000) >> 16;

Cela peut paraître effrayant, mais c'est un code de manipulation de bits standard. Vous obtenez finalement trois entiers, chacun compris entre 0 et 255. Avec cette méthode, vous pouvez représenter 255^3 = 16 millions d'objets différents, ce qui est certainement suffisant.

II-D. Afficher la scène avec cette couleur

Nous avons maintenant besoin d'un shader pour utiliser cette couleur. C'est très simple. Le vertex shader ne fait rien :

Sélectionnez
#version 330 core

// Données d'entrée du sommet, différentes pour chaque exécution de ce shader.
layout(location = 0) in vec3 vertexPosition_modelspace;

// Valeurs constantes pour tout le modèle.
uniform mat4 MVP;

void main(){

    // Position de sortie du sommet, dans l'espace clip : MVP * position
    gl_Position =  MVP * vec4(vertexPosition_modelspace,1);

}

Et le fragment shader écrit simplement la couleur voulue dans le tampon d'image :

Sélectionnez
#version 330 core

// Données de sortie
out vec4 color;

// Valeurs constantes pour tout le modèle.
uniform vec4 PickingColor;

void main(){

    color = PickingColor;

}

Facile !

La seule astuce est que vous devez envoyer vos couleurs en nombres flottants (compris dans [0, 1]) alors que vous avez des entiers (compris dans [0, 255]), donc vous devez effectuer une petite division lors de l'appel à glUniformXX() :

Sélectionnez
// OpenGL attend des couleurs entre [0,1], donc divisez-les par 255.
glUniform4f(pickingColorID, r/255.0f, g/255.0f, b/255.0f, 1.0f);

Vous pouvez maintenant dessiner les modèles comme d'habitude (glBindBuffer, glVertexAttribBuffer, glDrawElements) et vous obtiendrez l'étrange image ci-dessus.

II-E. Obtenir la couleur sous le curseur de souris

Lorsque vous avez dessiné tous les modèles (certainement avec une boucle for()), vous devez appeler glReadPixels(), permettant d'obtenir les pixels rasterizés sur le CPU. Mais pour que cela fonctionne, quelques étapes supplémentaires sont nécessaires.

Premièrement, vous devez appeler glFlush(). Cela indiquera au pilote OpenGL d'envoyer les commandes en attente (incluant votre dernier glDrawXX) au GPU. Cela n'est typiquement pas fait automatiquement, car les commandes sont envoyées en groupe et non immédiatement (cela signifie que lorsque vous appelez glDrawElements(), rien n'est dessiné. Cela VA être dessiné quelques millisecondes plus tard). Cette opération est LENTE.

Ensuite, vous devez appeler glFinish(), qui attendra jusqu'à ce que tout soit vraiment affiché. La différence avec glFlush() est que glFlush() envoie simplement les commandes ; glFinish() attend que les commandes soient exécutées. Cette opération est LEEENTE.

Vous devez aussi configurer comment glReadPixels() se comportera par rapport à l'alignement mémoire. C'est quelque peu hors sujet, mais simplement, vous devez appeler glPixelStore(GL_UNPACK_ALIGNMENT, 1).

Finalement, vous pouvez appeler glReadPixels() ! Voici le code complet :

Sélectionnez
// Attend jusqu'à ce que toutes les commandes de dessin soient vraiment exécutées.
// C'est ultra méga lent !
// Il y a habituellement un long moment entre glDrawElements() et le rendu de tous les fragments.
glFlush();
glFinish();

glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

// Lit le pixel au centre de l'écran.
// Vous pouvez aussi utiliser glfwGetMousePos().
// C'est aussi ultra méga lent, même pour 1 pixel,
// car le tampon d'image est sur le GPU.
unsigned char data[4];
glReadPixels(1024/2, 768/2,1,1, GL_RGBA, GL_UNSIGNED_BYTE, data);

Votre couleur est maintenant dans le tableau data. Ici, vous pouvez voir que l'identifiant est 19 :

Contenu du pixel

II-F. Convertir la couleur en un identifiant

Vous pouvez maintenant retrouver votre identifiant à partir du tampon data. Le code est exactement l'opposé du code de conversion de l'identifiant en une couleur :

Sélectionnez
// Convertit la couleur en un identifiant
int pickedID =
    data[0] +
    data[1] * 256 +
    data[2] * 256*256;

II-G. Utiliser cet identifiant

Vous pouvez maintenant utiliser l'identifiant comme vous le souhaitez. Dans l'exemple, le texte de l'interface est mis à jour, mais bien sûr, vous pouvez faire ce que vous souhaitez.

Sélectionnez
if (pickedID == 0x00ffffff){ // Blanc, cela doit être le fond !
    message = "background";
}else{
    std::ostringstream oss; // Les chaines de caractères C++ sont nulles
    oss << "mesh " << pickedID;
    message = oss.str();
}

III. Avantages et inconvénients

  • Avantages :

    • facile et rapide à implémenter ;
    • aucun besoin de bibliothèque externe ni de mathématique complexe.

  • Inconvénients :

    • utilise glFlush(), glFinish(), glReadPixels() qui sont toutes des fonctions notoirement lentes, car elles forcent le CPU à attendre le GPU, détériorant ainsi les performances ;
    • vous n'avez pas une information précise : quel triangle est touché, la normale en ce point, etc.

IV. Remarques finales

Bien que cela ne soit pas recommandé, cette technique peut être vraiment utile, mais elle est complètement restreinte au picking. Les méthodes dans les deux prochains tutoriels peuvent être utilisées pour d'autres buts, comme la détection des collisions, faire marcher un avatar sur le sol, effectuer des requêtes de visibilité pour l'intelligence artificielle, etc.

Si finalement vous utilisez cette technique et que vous devez obtenir plusieurs points dans une même image, vous devez obtenir ces points en une seule fois. Par exemple, si vous devez gérer cinq points de toucher, ne dessinez pas cinq fois la scène !


OpenGL Moderne - Annexe F : Cliquer sur un objet avec une bibliothèque physique

Vu dans ... https://opengl.developpez.com/tutoriels/opengl-tutorial/annexe-F-picking-bibliotheque-physique/

Annexe F : Cliquer sur un objet avec une bibliothèque physique

I. Cliquer sur un objet avec une bibliothèque physique

Dans ce tutoriel, nous allons voir la méthode « recommandée » pour cliquer sur un objet dans un moteur de jeu classique - ce qui peut ne pas être votre cas.

L'idée est que le moteur de jeu devra intégrer un moteur physique dans tous les cas et tous les moteurs physiques ont des fonctions pour obtenir l'intersection d'un rayon avec la scène. En plus, ces fonctions sont certainement mieux optimisées que ce que vous pouvez obtenir vous-même : tous les moteurs physiques utilisent des structures de partitionnement de l'espace évitant de tester les intersections avec les objets qui ne sont pas dans la même région.

Dans ce tutoriel, nous allons utiliser le moteur Bullet Physics Engine, mais les concepts sont identiques pour n'importe quel autre : PhysX, HavoK, etc.

II. Intégration de Bullet

De nombreux tutoriels expliquent comment intégrer Bullet ; en particulier, le wiki de Bullet est très bien fait.

Sélectionnez
// Initialise Bullet. Cela suit exactement http://bulletphysics.org/mediawiki-1.5.8/index.php/Hello_World,
// même si nous n'utiliserons pas la plupart de tout cela.

// Construit la broadphase
btBroadphaseInterface* broadphase = new btDbvtBroadphase();

// Initialise la configuration de collision et le dispatcher
btDefaultCollisionConfiguration* collisionConfiguration = new btDefaultCollisionConfiguration();
btCollisionDispatcher* dispatcher = new btCollisionDispatcher(collisionConfiguration);

// Le solveur physique
btSequentialImpulseConstraintSolver* solver = new btSequentialImpulseConstraintSolver;

// Le monde
btDiscreteDynamicsWorld* dynamicsWorld = new btDiscreteDynamicsWorld(dispatcher,broadphase,solver,collisionConfiguration);
dynamicsWorld->setGravity(btVector3(0,-9.81f,0));

Chaque objet doit avoir une forme de collision (Collision Shape). Même si celle-ci peut être le modèle en lui-même, cela est généralement une mauvaise idée pour les performances. À la place, on utilise habituellement des formes plus simples telles que les boîtes, les sphères ou les capsules. Voici quelques formes de collision. De gauche à droite : sphère, cube, convex hull du modèle, modèle d'origine. Les sphères sont moins précises que le modèle, mais bien plus rapides à tester.

Exemple de formes de collision

Dans cet exemple, tous les modèles utilisent la même boîte :

Sélectionnez
btCollisionShape* boxCollisionShape = new btBoxShape(btVector3(1.0f, 1.0f, 1.0f));

Les moteurs physiques ne connaissent rien sur OpenGL ; et en réalité, ceux-ci peuvent s'exécuter sans aucune visualisation 3D. Donc vous ne pouvez pas directement donner votre VBO à Bullet. À la place, vous devez ajouter un corps solide (Rigid Body) dans la simulation.

Sélectionnez
btDefaultMotionState* motionstate = new btDefaultMotionState(btTransform(
    btQuaternion(orientations[i].x, orientations[i].y, orientations[i].z, orientations[i].w),
    btVector3(positions[i].x, positions[i].y, positions[i].z)
));

btRigidBody::btRigidBodyConstructionInfo rigidBodyCI(
    0,                  // masse, en kg. 0 -> objet statique, ne bougera jamais.
    motionstate,
    boxCollisionShape,  // forme de collision du corps
    btVector3(0,0,0)    // inertie locale
);
btRigidBody *rigidBody = new btRigidBody(rigidBodyCI);

dynamicsWorld->addRigidBody(rigidBody);

Notez que le corps rigide utilise une forme de collision pour définir sa forme.

Nous gardons aussi une trace du corps rigide, mais comme il est indiqué dans le commentaire, un vrai moteur aura d'une quelconque façon une classe MonGameObject avec la position, l'orientation, le modèle OpenGL et un pointeur vers le corps rigide.

Sélectionnez
rigidbodies.push_back(rigidBody);

// Petite astuce : garder l'indice du modèle « i » dans le pointer pour l'utilisateur de Bullet.
// Il sera utilisé pour connaître quel objet a été cliqué.
// Un vrai programme passera sûrement un « MonPointeurGameObject » à la place.
rigidBody->setUserPointer((void*)i);

En d'autres mots : veuillez ne pas utiliser le code ci-dessus dans la vraie vie ! Ce n'est que pour les besoins de la démonstration.

III. Lancer de rayon

III-A. Trouver la position du rayon

Premièrement, nous devons trouver un rayon qui commence à la position de la caméra et va « à travers la souris ». Cela est fait dans la fonction ScreenPosToWorldRay().

Premièrement, nous trouvons la position de départ et de fin du rayon dans les coordonnées normalisées du périphérique. Nous le faisons dans cet espace, car c'est très simple :

Sélectionnez
// Les positions de départ et de fin du rayon, dans les coordonnées normalisées du périphérique (avez-vous lu le quatrième tutoriel ?)
glm::vec4 lRayStart_NDC(
    ((float)mouseX/(float)screenWidth  - 0.5f) * 2.0f, // [0,1024] -> [-1,1]
    ((float)mouseY/(float)screenHeight - 0.5f) * 2.0f, // [0, 768] -> [-1,1]
    -1.0, // Le plan auprès correspond à Z=-1 dans l'espace de coordonnées normalisée du périphérique
    1.0f
);
glm::vec4 lRayEnd_NDC(
    ((float)mouseX/(float)screenWidth  - 0.5f) * 2.0f,
    ((float)mouseY/(float)screenHeight - 0.5f) * 2.0f,
    0.0,
    1.0f
);

Pour comprendre ce code, regardons une nouvelle fois à cette image du quatrième tutoriel :

Image non disponible

L'espace de coordonnées normalisé du périphérique est un cube 2 x 2 x 2, centré sur l'origine, donc dans cet espace, le rayon « passant par la souris » n'est qu'une ligne droite, perpendiculaire au plan proche ! Cela rend IRayStart_NDC et IEndStart_NDC facile à calculer.

Maintenant, nous n'avons qu'à appliquer la transformation inverse :

Sélectionnez
// La matrice de projection va de l'espace caméra à l'espace de coordonnées normalisé du périphérique.
// Donc inverse(ProjectionMatrix) va de l'espace de coordonnées normalisé du périphérique à l'espace caméra.
glm::mat4 InverseProjectionMatrix = glm::inverse(ProjectionMatrix);

// La matrice de vue va de l'espace monde à l'espace caméra.
// Donc inverse(ViewMatrix) va de l'espace caméra vers l'espace monde.
glm::mat4 InverseViewMatrix = glm::inverse(ViewMatrix);

glm::vec4 lRayStart_camera = InverseProjectionMatrix * lRayStart_NDC;    lRayStart_camera/=lRayStart_camera.w;
glm::vec4 lRayStart_world  = InverseViewMatrix       * lRayStart_camera; lRayStart_world /=lRayStart_world .w;
glm::vec4 lRayEnd_camera   = InverseProjectionMatrix * lRayEnd_NDC;      lRayEnd_camera  /=lRayEnd_camera  .w;
glm::vec4 lRayEnd_world    = InverseViewMatrix       * lRayEnd_camera;   lRayEnd_world   /=lRayEnd_world   .w;

// Méthode plus rapide (qu'une seule inversion)
//glm::mat4 M = glm::inverse(ProjectionMatrix * ViewMatrix);
//glm::vec4 lRayStart_world = M * lRayStart_NDC; lRayStart_world/=lRayStart_world.w;
//glm::vec4 lRayEnd_world   = M * lRayEnd_NDC  ; lRayEnd_world  /=lRayEnd_world.w;

Avec IRayStart_worldspace et IRayEnd_worldspace, la direction du rayon (dans l'espace monde) est facile à calculer :

Sélectionnez
glm::vec3 lRayDir_world(lRayEnd_world - lRayStart_world);
lRayDir_world = glm::normalize(lRayDir_world);

III-B. Utiliser RayTest()

Le lancer de rayon est très simple, aucun besoin de commentaire :

Sélectionnez
out_direction = out_direction*1000.0f;

btCollisionWorld::ClosestRayResultCallback RayCallback(
    btVector3(out_origin.x, out_origin.y, out_origin.z),
    btVector3(out_direction.x, out_direction.y, out_direction.z)
);
dynamicsWorld->rayTest(
    btVector3(out_origin.x, out_origin.y, out_origin.z),
    btVector3(out_direction.x, out_direction.y, out_direction.z),
    RayCallback
);

if(RayCallback.hasHit()) {
    std::ostringstream oss;
    oss << "mesh " << (int)RayCallback.m_collisionObject->getUserPointer();
    message = oss.str();
}else{
    message = "background";
}

La seule chose est que pour une raison étrange, vous devez définir la position de départ et de fin du rayon, deux fois.

C'est tout, vous savez comment implémenter le clic sur les objets dans Bullet !

IV. Avantages et inconvénients

  • Avantages :

    • très simple lorsque vous avez déjà un moteur physique ;
    • rapide ;
    • n'impacte pas les performances OpenGL.

  • Inconvénients :

    • certainement pas la bonne solution si vous n'avez pas besoin de simulation physique ou d'un moteur de collision.

V. Remarques finales

Tous les moteurs physiques possèdent une vue de débogage. Le code d'exemple montre comment l'activer avec Bullet. Vous obtiendrez une représentation de ce que Bullet sait sur votre scène, ce qui est vraiment très utile pour déboguer les problèmes liés à la physique, notamment pour être sûr que le « monde réel » est consistant avec le « monde physique ».

Vue de débogage de Bullet

La boîte verte est la forme de collision, à la même position et orientation que le modèle. La boîte rouge est la boîte englobante alignée sur les axes (AABB), qui est utilisée pour un rapide test de réjection : si le rayon ne touche pas le AABB (très facile à calculer), alors il ne touchera pas la forme de collision. Finalement, vous pouvez voir les axes de l'objet en bleu et rouge (regardez au nez et à l'oreille). Pratique !


OpenGL Moderne - annexe G : cliquer sur un objet avec une fonction personnalisée

Vu dans ... https://opengl.developpez.com/tutoriels/opengl-tutorial/annexe-G-picking-personnalisee/

Annexe G : cliquer sur un objet avec une fonction personnalisée

I. Introduction

La dernière méthode est un sympathique compromis entre l'implémentation OpenGL « hacky » et l'utilisation d'un moteur physique complet pour de simples lancers de rayons.

Ce tutoriel utilise les concepts et les fonctions du tutoriel de Bullet, donc soyez sûr de le lire avant celui-ci.

II. L'idée de base

Au lieu de dépendre de l'intersection d'un rayon contre une forme de collision de Bullet, nous allons l'implémenter nous-mêmes.

Comme nous l'avons vu, il y a plusieurs formes de collision possibles. Les sphères sont très simples à intersecter, mais pour de nombreux objets, elles représentent très mal les modèles. D'un autre côté, l'intersection d'un rayon avec tous les triangles du modèle original est très couteuse. Les boîtes englobantes orientées (Oriented Bounding Box (OBB)) sont un bon compromis. Elles sont assez précises (mais cela dépend de votre géométrie) et sont plutôt rapides à calculer.

Une OBB est une boîte englobant le modèle et lorsque le modèle est déplacé ou tourné, la même transformation est appliquée à la boîte :

Exemple de suivi du modèle par l'OBB

III. Algorithme d'intersection rayon-OBB

(L'algorithme et les figures s'inspirent grandement du livre Real-Time Rendering Vol. 3. Achetez ce livre !)

Soit l'OBB ci-dessous. Sur l'axe des X, qui est délimité par deux plans verticaux, ici en rouge. Lorsqu'ils sont intersectés par le rayon (une opération très simple), cela donne deux intersections, une « proche » et une « lointaine » :

Image non disponible

Lorsque le rayon intersecte les deux autres plans qui délimitent l'axe Y (en vert), cela donne deux intersections supplémentaires. Notez comment les intersections sont ordonnées : vous entrez dans la zone verte → vous sortez de la zone verte → vous entrez dans la zone rouge → vous sortez de la zone rouge. Cela signifie qu'il n'y a pas d'intersection.

Image non disponible

Mais si l'ordre change (vous entrez dans la zone verte → vous entrez dans la zone rouge), alors vous savez qu'il y a une intersection !

Image non disponible

Mettons cela en pratique.

IV. Implémentation de l'algorithme

(Le code complet est disponible dans le fichier Misc05/misc05_picking_custom.cpp.)

Notre fonction d'intersection rayon - OBB ressemblera à cela :

Sélectionnez
bool TestRayOBBIntersection(
    glm::vec3 ray_origin,        // Origine du rayon, dans l'espace monde
    glm::vec3 ray_direction,     // Direction du rayon (PAS la cible !), dans l'espace monde. Doit être normalisé.
    glm::vec3 aabb_min,          // Coordonnées minimales X,Y,Z du modèle lorsqu'il n'est pas transformé.
    glm::vec3 aabb_max,          // Coordonnées maximales X,Y,Z. Souvent aabb_min*-1 si votre modèle est centré, mais ce n'est pas toujours le cas.
    glm::mat4 ModelMatrix,       // Transformation appliquée au modèle (et qui sera donc appliquée à la boîte englobante)
    float& intersection_distance // Sortie : la distance entre ray_origin et l'intersection avec l'OBB
){

Nous commençons par initialiser quelques variables. tMin est la plus grande intersection « proche » actuellement trouvée ; tMax est la plus petite intersection « lointaine » trouvée jusqu'à présent. Delta est utilisé pour calculer les intersections avec les plans.

Sélectionnez
float tMin = 0.0f;
float tMax = 100000.0f;

glm::vec3 OBBposition_worldspace(ModelMatrix[3].x, ModelMatrix[3].y, ModelMatrix[3].z);

glm::vec3 delta = OBBposition_worldspace - ray_origin;

Maintenant, calculons l'intersection entre les deux plans qui délimitent l'OBB sur l'axe des X :

Sélectionnez
glm::vec3 xaxis(ModelMatrix[0].x, ModelMatrix[0].y, ModelMatrix[0].z);
float e = glm::dot(xaxis, delta);
float f = glm::dot(ray_direction, xaxis);

// Attention, ne faites pas la division si f est proche de 0 ! Voyez le code source complet pour plus de détails.
float t1 = (e+aabb_min.x)/f; // Intersection avec le plan « gauche »
float t2 = (e+aabb_max.x)/f; // Intersection avec le plan « droit »

t1 et t2 contiennent maintenant les distances entre l'origine du rayon et les intersections avec le plan, mais nous ne savons pas dans quel ordre, donc nous nous assurons que t1 représente l'intersection « proche » et t2 l'intersection « lointaine » :

Sélectionnez
if (t1>t2){ // si dans le désordre
    float w=t1;t1=t2;t2=w; // échange t1 et t2
}

Nous pouvons mettre à jour tMin et tMax :

Sélectionnez
// tMax est l'intersection « lointaine » la plus proche (parmi les paires de plans X,Y et Z)
if ( t2 < tMax ) tMax = t2;
// tMin est l'intersection « proche » la plus proche (parmi les paires de plans X,Y et Z)
if ( t1 > tMin ) tMin = t1;

Et voici une astuce : si l'intersection « lointaine » est plus proche que l'intersection « proche », il n'y a pas d'intersection.

Sélectionnez
if (tMax < tMin )
    return false;

Cela est pour l'axe des X. C'est exactement la même chose pour les autres axes !

V. Utiliser l'algorithme

La fonction TestRayOBBIntersections() nous permet de tester l'intersection avec une seule OBB, donc nous devons toutes les tester. Dans ce tutoriel, nous testons simplement les boîtes les unes après les autres, mais si vous avez plein d'objets, vous pourriez avoir besoin d'une structure accélératrice comme un arbre binaire de partitionnement de l'espace (Binary Space Partitionning Tree BSP-Tree) ou une hiérarchie de volume englobant (Bounding Volume Hierarchy BVH).

Sélectionnez
for(int i=0; i<100; i++){

    float intersection_distance; // Sortie de TestRayOBBIntersection()
    glm::vec3 aabb_min(-1.0f, -1.0f, -1.0f);
    glm::vec3 aabb_max( 1.0f,  1.0f,  1.0f);

    // Matrice de transformation ModelMatrix transforme :
    // - le modèle à sa position et orientation voulue
    // - mais aussi l'AABB (définie avec aabb_min et aabb_max) en une OBB
    glm::mat4 RotationMatrix = glm::toMat4(orientations[i]);
    glm::mat4 TranslationMatrix = translate(mat4(), positions[i]);
    glm::mat4 ModelMatrix = TranslationMatrix * RotationMatrix;

    if ( TestRayOBBIntersection(
        ray_origin,
        ray_direction,
        aabb_min,
        aabb_max,
        ModelMatrix,
        intersection_distance)
    ){
        std::ostringstream oss;
        oss << "mesh " << i;
        message = oss.str();
        break;
    }
}

Notez que cet algorithme a un problème : il prend la première OBB qu'il trouve. Mais si cette OBB est derrière une autre, cela est faux. Donc vous devez prendre seulement l'OBB la plus proche ! C'est au lecteur de résoudre ce problème…

VI. Avantages et inconvénients

  • Avantages :

    • facile ;
    • nécessite peu de mémoire (seulement le surplus de l'OBB) ;
    • ne ralentit pas OpenGL comme le faisait la première version.

  • Inconvénients :

    • plus lent qu'avec le moteur physique, car il n'y a pas de structure accélératrice ;
    • peut ne pas être assez précis.

VII. Remarques finales

Il y a de nombreuses autres routines d'intersection disponibles pour toutes sortes de formes de collisions ; lisez par exemple http://www.realtimerendering.com/intersections.html.

Si vous avez besoin d'une intersection précise, vous devez effectuer un test rayon-triangle. Encore une fois, ce n'est pas une bonne idée de vérifier tous les triangles de tous les modèles linéairement. Une structure accélératrice supplémentaire est nécessaire.


Vu dans ... http://www-evasion.imag.fr/Membres/Antoine.Bouthors/teaching/opengl/opengl2win.html

Initialiser OpenGL avec Windows

1. Créer une fenêtre Windows

⇑

Comme je l'ai dit dans le premier chapitre, il faut obtenir un Device Context (DC), puis un Rendering Context (RC) afin d'initialiser OpenGL. Et pour cela, nous avons besoin de maîtriser quelque peu la gestion de fenêtres dans Windows. Nous allons donc voir comment créer une fenêtre et récupérer les infos nécessaires à l'initialisation.
Il y a en fait deux moyens d'obtenir un DC : soit en se créant sa petite fenêtre en Win32, soit en utilisant les MFC. Ici, je ne vais parler que de Win32, car c'est beaucoup plus simple et pratique que les MFC, qui sont très lourdes et chiantes à utiliser. Mais si vous savez gérer les MFC et compris comment récupérer un DC et un RC, alors vous ne devriez pas avoir de problème à intégrer OpenGL dans vos programmes utilisant les MFC.
Autre chose : j'utilise Ms Visual C++, que je trouve plus pratique pour ce genre de choses. Les procédures spécifiques au compilateur, du genre inclure les .lib, seront donc expliquées pour ce logiciel, et pas pour le Borland C++ Builder. Mais le code restera bien sûr le même quelque soit le compilateur.
Enfin, sachez que la touche F1 est votre amie : à chaque fois que je vous présente une nouvelle fonction, étudiez-la plus en détail avec l'aide de votre compliateur, car je ne peux pas tout vous dire sur tout, ou je n'aurais jamais fini. Servez-vous en donc le plus possible, et si jamais vous avez encore des doutes, envoyez-moi vos questions.

Pour les newbies de VC++, créez d'abord un nouveau projet par File>New>Project>Win32 Application, puis un nouveau fichier C++ par File>New>File>C/C++ Source File.

Bon, commencont par le commencement, à savoir les #include. Il y en a 3 obligatoires, sans lesquels votre programme ne pourra pas marcher. Les voici :

#include <windows.h>
#include <gl/gl.h>
#include <gl/glu.h>

gl.h et glu.h servent bien évidemment à utiliser les fonctions OpenGL. Quand à windows.h, il est utilisé pour créer les fenêtres, et vous n'en avez normalement pas besoin si vous utilisez glut.h. Les header OpenGL sont généralement dans le répertoire \include\gl de votre compilateur.
Mais ce n'est pas tout : les headers se rapportent à des fonctions qui sont définies dans les opengl32.dll et glu32.dll : il faut donc les lier à votre projet. Pour ce faire, avec VC++, allez dans Project>Settings>Links, et incluez opengl32.lib et glu32.lib dans la liste Object/library modules.

Ensuite, il faut savoir que dans un programme Win32, la fonction de départ n'est plus main mais WinMain. Créez donc la première fonction de votre programme :

int WINAPI WinMain( | HINSTANCE hInstance,
 | HINSTANCE hPrevInstance,
 | LPSTR lpCmdLine,
 | int nCmdShow)
{ |

La valeur de retour est la même que pour la fonction main() classique. Voyons maintenant ce que veulent bien dire ces paramètres bizarres :

  • hInstance est une sorte de handle pour votre programme : comme Windows est multitâche, il a besoin de savoir quel programme lui dit quoi. Et c'est à travers cette variable que Windows sait à qui il a affaire : c'est en quelque sorte la carte d'identité du programme. C'est pourquoi il est conseillé de la sauvegarder dans une variable globale, ce que nous allons faire :
HInst = hInstance; //HInst est un variable globale de type HINSTANCE
  • hPervInstance ne sert plus à rien, il est conservé pour des raisons de compaptibilité avec les programmes 16 bits
  • lpCmdLine est un pointeur sur la ligne de commande, pour récupérer les paramètres éventuels
  • nCmdShow est le style initial de la fenêtre : normale, réduite...etc. Il ne nous sert pas à grand chose pour le moment

Hé bien puisque nous avons notre fonction principale, pourquoi ne pas créer tout de suite notre fenêtre ? Allons-y :

HWND OpenGLWindow = CreateWindow
( "BUTTON", //Classe de la fenêtre
"Fenêtre OpenGL", | //Nom de la fenêtre
WS_VISIBLE | WS_BORDER, | //Caractéristiques
0, | //Position x
0, | //Position y
640, | //Largeur
480, | //Hauteur
0, | //Handle de la fenêtre même
0, | //Identifiant de la fenêtre fille
HInst, | //HINSTANCE du programme
NULL /*Chaine de caractères envoyée
en paramètre lors de la création
de la fenêtre*/
);
if (!OpenGLWindow) exit(1);

Je sais que les puristes vont m'incendier, mais je trouve que c'est plus simple de commencer comme ca. Etudions maintenant cette fonction CreateWindow :

  • Elle renvoie une valeur de type HWND, c'est-à-dire un handle pour la fenêtre (tiens, on se rapproche du DC). Il s'apparente au HINSTANCE pour le programme, sauf que là c'est pour une fenêtre. Vous pouvez aussi le sauvegarder en tant que variable globale. S'il est nul, il y a eu erreur.
  • Le premier paramètre correspond à la classe de la fenêtre : c'est elle qui définit presque toutes ses propriétés. Ici, nous avons pris une classe préexistante, pour simplifer l'explication, mais plus tard nous allons devoir en créer une nous-mêmes. Je disais que les puristes allaient m'incendier car en fait on n'utilise jamais ce genre de classe prédéfinie, parce que ca sert à rien, puisqu'on ne peut même pas définir la fonction WinProc. En fait leur seul utilité est d'apprendre à utiliser CreateWindow.
  • Les caractéristiques de la fenêtre peuvent être multiples. Faites F1 pour obtenir toutes les options possibles
  • Les 4 paramètres suivant parlent d'eux-mêmes
  • Les 2 d'après ne servent que dans le cas d'applications à plusieurs fenêtres, donc on s'en fout
  • Et enfin le dernier paramètre permet de passer des arguments à la fonction WinProc avec le message WM_CREATE. Je sais, vous ne comprenez pas, mais bient vous saurez.

Ca y est, la fenêtre est créée. Le programme est fini ? Pas tout à fait. Car il faut gérer les interactions avec Windows. Et pour cela il faut savoir comment le programme communique avec Windows : en fait, ce n'est pas Windows qui transmet au programme les messages le concernant, mais plutt le programme qui doit demander à Windows s'il doit faire quelque chose, grâce à la fonction PeekMessage. En quelque sorte, le programme va voir dans son casier s'il n'y a pas des messages pour lui via PeekMessage, et s'il y en a, il les récupère avec GetMessage, puis les distribue aux membres de son équipe (les fenêtres), via DispatchMessage. C'est très imagé mais au moins c'est clair et je suis s�r de m'être fait comprendre. Voilà donc le code à écrire après la création de la fenêtre :

MSG msg;
do
{
while (PeekMessage(&msg,OpenGLWindow,0,0,PM_NOREMOVE))
{

if(!GetMessage(&msg,OpenGLWindow,0,0))
                                exit(0);
DispatchMessage(&msg);
}
}
while(1);

Lorsque le message WM_QUIT est envoyé au programme, cela signifie qu'il doit fermer et que tout est fini. Si c'est ce message que récupère GetMessage, il renvoie 0 : cela explique le if(!GetMessage(&msg,OpenGLWindow,0,0)) exit(0); C'est la seule condition pour qu'un programme quitte. Lorsque le message est récupéré par GetMessage, il est supprimé de la pile des messages (le 'casier' de tout-à-l'heure), à une exception près : le message WM_PAINT n'est supprimé que lorsqu'il a été traité. Pour l'instant nous n'avons pas besoin de savoir ce que msg contient, puisque le seul message qui nous importe est WM_QUIT, et il est géré par GetMessage.
Peut-être ces histoires de messages vous semblent-elles un peu obscures, mais ne vous inquiétez pas, les rares taches d'ombres vont bientôt s'éclaircir.

Mais finissons d'abord notre programme par les lignes qui suivent :

return 0; //(pour la forme)
}

Et voilà ! Le premier programme d'exemple est terminé et vous pouvez le lancer : vous verrez un zoli bouton avec "Fenêtre OpenGL" marqué dessus, et vous pourrez même cliquer dessus, et alors là : RIEN ! Hé oui ! Il ne se passe rien quand on clique sur le bouton ! Ben oui, c'est là le problème : ca sert à rien d'utiliser la classe prédéfinie, puisqu'on peut même pas lui dire ce qu'on veut qu'il se passe quand on clique sur le bouton.

Il va donc falloir qu'on définisse notre propre classe de fenêtre. C'est d'ailleurs toujours comme ce qu'on fait. Stoppez le programme et recopiez donc le code suivant juste avant l'appel à CreateWindow :

WNDCLASS WindowClass =
{ | 0, | //Style
 | WinProc, | //Procédure pour la gestion des messages
 | 0, | //octets supplémentaires à allouer à la classe
 | 0, | //octets supplémentaires à allouer à la fenêtre
 | HInst, | //Handle du programme
 | 0, | //Icne
 | 0, | //Curseur
 | 0, | //Couleur d'arrière-plan
 | NULL, | //Pointeur sur le menu associé à la classe
 | "La classe!" | //Nom de la classe fenêtre
}; |
if (!RegisterClass(&WindowClass)) exit(1);

Ca y est : nous avons défini notre classe de fenêtre, qui s'appelle "La classe!". On peut donc remplacer "BUTTON" par "La classe!" dans la procédure CreateWindow. Mais si vous essayez de lancer le programme tel quel, le compilateur va vous renvoyer une erreur : en effet, dans la déclaration de la classe, nous lui passons comme fonction de gestion des messages une fonction que nous avons nommé WinProc (nous aurions aussi bien pu l'appeler Salutcavaouimoicavaettoicavaouicavaettoiohmoitusaiscava, mais je trouve que WinProc est plus approprié), mais cette fonction n'existe pas. Il faut donc la créer :

LRESULT CALLBACK WinProc(
HWND hwnd, | //Handle de la fenêtre
UINT uMsg, | //Message
WPARAM wParam, | //Paramètre word
LPARAM lParam ) | //Paramètre long
{
return DefWindowProc(
hwnd,
uMsg,
wParam,
lParam
); 		//Lance la procédure par défaut
}

Voici donc la procédure si mystérieuse : un truc tout con en fait, qui ne fait que passer le relais à une autre procédure déjà définie. Et puis si vous êtes perspicace, vous auriez pu me dire "Mais alors, on aurait pu tout connement mettre DefWindowProc à la place de WinProc !". Hé ben en fait...oui. Mais maintenant, on va personnaliser un peu cette fonction, histoire de s'amuser.

D'abord, vous aurez srement remarqué que le fait d'appuyer sur Alt+F4 ferme la fenêtre, mais pas le programme : il faut l'arrêter manuellement avec le compilateur ou Ctrl+Alt+Del. En effet, le fait d'envoyer le message "Ferme la fenêtre" ne provoque pas chez DefWindowProc l'envoi du message "Quitte le programme" : nous allons donc remplacer le contenu de WinProc par :

if (uMsg==WM_CLOSE) {PostQuitMessage(0);return
                        0;}
else return DefWindowProc(hwnd,uMsg,wParam,lParam);

Ca y est ! nous avons enfin notre propre fonction de gestion de messages à nous, qui sait fermer le programme. Et vous remarquerez que les fameux messages évoqués plus haut (WM_PAINT,WM_QUIT,etc...) sont contenus dans la variable uMsg, transmise à WinProc, et que PostQuitMessage dit au programme de se fermer sans autre forme de procès.

Bon et bien maintenant, si nous intégrions OpenGL à notre fenêtre, pour voir ? C'est vrai, depuis le début du tutorial, on a toujours pas vu de DC ou de RC, juste un HWND, qui ma foi faisait penser à un DC de par sa définition. Je ne vous mentirais pas plus longtemps : le DC est en effet directement tiré du HWND. En fait, il suffit de le récupérer grâce à la fonction GetDC. "Hé bien on n'a qu'à le faire alors !" Ok, ok on va le faire...mais o ? après la création de la fenêtre, dans le WinMain ? Oui, pourquoi pas, mais ca ferait un petit peu bidouille, non ? Alors que lorsque l'on appelle CreateWindow, celle-ci exécute justement WinProc une seule fois, avec comme message WM_CREATE... C'est trop beau : tant qu'à faire, on va en profiter pour récupérer notre DC ! Etoffons donc notre chère WinProc :

switch(uMsg)
{
case WM_CLOSE:

ReleaseDC(hwnd,DC);
PostQuitMessage(0);
break;

//Libère le DC et ferme le programme
case WM_CREATE:

DC=GetDC(hwnd);
break;

//Récupère le DC
default:

return DefWindowProc(
hwnd,
uMsg,
wParam,
lParam);
break;

 //Sinon, fait le truc habituel
}
return 0;

Sans oublier de rajouter

HDC DC;

dans les déclaration de variables globales. Et voilà, nous avons notre DC ! Lancez le programme et vous verrez ce que ca change : ... rien. Car l'initialisation d'OpenGL n'est même pas commencée. Il nous faut encore récupérer le Rendering Context, et le définir comme sortie OpenGL courante, en faisant

RC = wglCreateContext(DC); //RC est une variable globale de type HGLRC
if (!RC) SendMessage(hwnd,WM_CLOSE,0,0);
wglMakeCurrent(DC, RC);

après le GetDC du WM_CREATE, et en rajoutant

wglMakeCurrent(NULL, NULL);
if (RC) wglDeleteContext(RC);

avant le ReleaseDC du WM_CLOSE.
Voilà, normalement ca devrait marcher. Mais ca marche pas. Pourquoi ? Et bien tout simplement parce qu'on a pas paramétré le Device Context. En effet, il faudrait d'abord lui dire en quelle résolution on veut être, et puis si on utilise le double buffering, et puis qu'on veut lui associer un RC. Bref, il faut paramétrer ce qu'on appelle le PixelFormat (cad en gros le format d'affichage). Créons donc une petite fonction qui va nous faire tout ca :

void SetupPixelFormat(HDC hDC)
{
PIXELFORMATDESCRIPTOR pfd =
{
sizeof(PIXELFORMATDESCRIPTOR), | //taille du descripteur de format
1, | //version
PFD_SUPPORT_OPENGL |
PFD_DRAW_TO_WINDOW |
PFD_DOUBLEBUFFER, 		//Propriété
PFD_TYPE_RGBA, | //Mode de couleurs
16, | //Bits de couleur
0, 0, 0, 0, 0, 0, | //Paramètres des couleurs
0,0, | //Paramètres alpha
0,0, 0, 0, 0, | //Paramètres du buffer d'accumulation
32, | //Bits de profondeur
0, | //Bits du buffer stencil
0, | //Nombre de buffers auxiliaires
0, | //ignoré (obsolète)
0, | //réservé/code>
0, | //ignoré (obsolète)
0, | //Couleur de transparence
0 | //Ignoré (obsolète)
};
int pixelFormat;
pixelFormat = ChoosePixelFormat(hDC, &pfd);
if (!pixelFormat)
{

MessageBox
(
WindowFromDC(hDC),
"Mode graphique non supporté",
"Problème",
MB_ICONERROR | MB_OK
);
exit(1);

 /*Vérifie si un PixelFormat du type demandé existe*/
}
if (!SetPixelFormat(hDC, pixelFormat, &pfd))
{

MessageBox
(
WindowFromDC(hDC),
"Mode graphique non supporté",
"Problème",
MB_ICONERROR | MB_OK
);
exit(1);

 /*Applique le PixelFormat. Arrête si erreur*/
}
}

Je me fais chier à décrire cette fonction, mais en fait il n'y a pas à en dire beaucoup plus. Les paramètres sont toujours les mêmes, sauf peut-être le nombre de bits de couleur et de profondeur. A part ca, le reste ne sert pas à grand chose, mais c'est nécessaire. Contentez-vous de recopier la fonction, et interrogez votre compilateur ou moi si quelque chose vous gêne. Mais je vous rassure, vous n'avez pas grand chose à savoir sur ces paramètres.

Tout ce qu'il reste à faire, c'est d'inclure un appel à cette fonction après la récupération du DC et avant celle du RC (sans cela wglCreateContext vous renverra une erreur). Et voilà ! Là c'est réellement fini, et votre fonction WinProc doit ressembler à ceci :

LRESULT CALLBACK WinProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam )
{
switch(uMsg)
{
case WM_CLOSE:

wglMakeCurrent(NULL, NULL);
if (RC) wglDeleteContext(RC);
ReleaseDC(hwnd,DC);
PostQuitMessage(0);
break;

//Libère RC et DC et ferme le programme
case WM_CREATE:

DC=GetDC(hwnd);
SetupPixelFormat(DC);
RC = wglCreateContext(DC);
if (!RC) SendMessage(hwnd,WM_CLOSE,0,0);
wglMakeCurrent(DC, RC);
break;

 //Récupère le DC et le RC
default:

return DefWindowProc(hwnd,
uMsg,
wParam,
lParam);
break;

 //Sinon, fait le truc habituel
}
return 0;
}

Si vous lancez le programme maintenant, vous ne verrez cependant pas de changement par rapport à avant, tout simplement parce que le message WM_PAINT est encore géré par DefWindowProc. Mais sachez qu'OpenGL est bien lié à cette fenêtre, et que le pas entre la fenêtre vide et un rendu OpenGL est très mince. Pour le découvrir, passez à la deuxiême partie de ce tutorial !

2. Attacher OpenGL à cette fenêtre

⇑

Dans la premiè partie, je vous ai appris à créer votre fenêtre par CreateWindow(), à associer la sortie OpenGL à cette fenêtre par GetDC(), SetupPixelFormat(), wglCreateContext() et wglMakeCurrent(), et à gérer les messages envoyés à cette fenêtre par une fonction que nous avons appelé WinProc(). Je vais maintenant finir de vous expliquer comment initialiser OpenGL lui-même, maintenant que la fenêtre est bien créée.
Reprenez donc votre programme de la dernière fois, et modifiez votre fonction WinProc() de façon à ce qu'elle ressemble à ça :

LRESULT CALLBACK WinProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam )
{
switch(uMsg)
{
case WM_CLOSE:

wglMakeCurrent(NULL, NULL);
if (RC) wglDeleteContext(RC);
ReleaseDC(hwnd,DC);
PostQuitMessage(0);
break;

//Libère RC et DC et ferme le programme
case WM_CREATE:

DC=GetDC(hwnd);
SetupPixelFormat(DC);
RC = wglCreateContext(DC);
if (!RC) SendMessage(hwnd,WM_CLOSE,0,0);
wglMakeCurrent(DC, RC);
InitGL();
break;

 //Récupère le DC et le RC
case WM_SIZE:

Reshape(
LOWORD(lParam),
HIWORD(lParam)
);
break;

//Met à jour les paramètres OGL
case WM_PAINT:

Draw();
break;

//Exécute le rendu
default:

return DefWindowProc(
hwnd,
uMsg,
wParam,
lParam
);
break;

 //Sinon, fait le truc habituel
}
return 0;
}

Nous avons rajouté un appel à 3 fonctions que nous allons écrire un peu plus loin : Draw(), Reshape() et InitGL(). Nous reparlerons beaucoup de Draw() et InitGL() dans les prochains tutoriaux. Mais parlons d'abord de Reshape() : voici ce qu'il faut écrire à l'intérieur :

void Reshape(int width, int height)
{ |
 | glViewport(0,0,width,height);
 | glMatrixMode(GL_PROJECTION);
 | glLoadIdentity();
 | gluPerspective(45,float(width)/float(height),0.1,100);
} |
  • glViewport() informe d'abord OpenGL sur la taille de la zone de sortie
  • Ensuite glMatrixMode() charge la matrice de projection. Une matrice ? quésako ? En fait, c'est un tableau sur lequel on peut effectuer des opérations mathématiques spécifiques. La matrice de projection sert à définir comment les coordonnées 3D sont transformées en coordonnées 2D sur l'écran. La matrice modèle et vue (GL_MODELVIEW) définit la position du repère de coordonnées dans le monde. C'est sur cette matrice que l'on effectuent des opérations qui se traduisent par des rotations, des translations ou des homotéties. La matrice texture permet de définir l'aspect des textures plaquées sur les faces. On peut aussi lui faire subir des rotations, etc, qui se répercutent sur l'affichage. Les matrices sont donc des outils très puissant de modélisation.
  • glLoadIdentity() fait un reset de la matrice (il la réinitialise) pour pouvoir effectuer des modifications sur celle-ci
  • Et enfin gluPerspective modifie la matrice courante (c'est-à-dire ici la matrice de projection) pour qu'OpenGL transforme les coordonnées 3D au moment du rendu en coordonnées 2D par rapport à l'écran, de façon à ce qu'on ait 'impression de regarder à travers un objectif de 45 de focale, de largeur width et de hauteur height, et dont le clipping va de 0.1 à 100 (attention la valeur du clipping near - ici 0.1 - doit être strictement supérieure à 0).

Cette fonction Reshape() est appelée à chaque fois que la fenêtre est redimensionnée, et notemment à sa création. Grâce à cette fonction, OpenGL est totalement initialisé et est parfaitement près à dessiner. Mais alors qu'est-ce que vient faire InitGL() ? Et bien en fait on va mettre dans cette fonction toutes les fonctions optionnelles destinées à paramétrer le rendu : activation du test de profondeur, définition des lumières, textures, transparence... Donc pour l'instant, comme on n'a rien à y mettre, on va quand même la créer, mais en la laissant vide :

void InitGL()
{
}

Et maintenant, la fontion Draw() : c'est la fonction qui sera appelée à chaque fois que la fenêtre sera rafraîchie, et donc c'est là-dedans qu'on va placer toute la sauce qui va afficher à l'écran de magnifiques images. La fonction Draw() commencera toujours par glClear() (sauf effet visuel particulier) et finira toujours par SwapBuffers(DC). Mais trêve de bavardages, la voici :

void Draw()
{
} glClear
(
GL_COLOR_BUFFER_BIT |
GL_DEPTH_BUFFER_BIT
); 		//Efface le frame buffer et le Z-buffer
glMatrixMode(GL_MODELVIEW); | //Choisit la matrice MODELVIEW
glLoadIdentity(); | //Réinitialise la matrice
//Placer ici tout le code de transformation et de dessin

SwapBuffers(DC);

 //Echange les 2 frame buffers
  • glClear() efface les buffers passés en paramètre. Ici, nous n'utilisons que le frame buffer (c'est-à-dire le buffer correspondant à l'image affiché à l'écran) et le z-buffer (utilisé pour les tests de profondeur). Pour l'instant, nous n'utiliseront pas le z-buffer, mais comme plus tard on l'utilisera toujours, autant vous montrer tout de suite comment on s'en sert.
  • Ensuite on charge la matrice modèle/vue et on la réinitialise pour pouvoir par la suite effectuer toutes les transformations 3D nécessaires (rotations, translations...).
  • Après vient tout le code de dessin et de transformation, que l'on remplira au fur et à mesure des tutorials
  • Et enfin la fonction SwapBuffers(DC) échange les deux frame buffers : celui sur lequel on travaillait est affiché à l'écran tandis que celui qui était à l'écran est caché et servira pour le prochain rendu.

Vous pouvez dès maintenant essayer ce programme : vous y verrez un magnifique écran noir, car pour l'instant rien n'est dessiné dans notre écran
Mais pour ne pas vous laisser sur votre fin, voici quelques lignes à insérer dans Draw() entre glLoadIdentity() et SwapBuffers() pour vous montrer que ca marche vraiment :

gluLookAt(0,0,-10,0,0,0,0,1,0);
glBegin(GL_TRIANGLES);

glVertex2i(0,1);
glVertex2i(-1,0);
glVertex2i(1,0);
glEnd();

Je vous laisse découvrir ce que ca donne (pour les impatients, allez voir dans le tut glut).

Et voila ! Vous avez réellement dessiné en OpenGL, rien qu'avec un petit tutorial de rien du tout ! C'est-y pas génial ? Avouez que c'est pas compliqué. Tout ce que vous avez besoin de savoir pour programmer en OpenGL sous Windows, c'est créer une fenêtre, savoir récupérer et gérer les messages qui lui sont transmis, et récupérer un Device Context et un Rendering Context pour initialiser le PixelFormat. Et en plus, vous n'avez même pas besoin de connaître par coeur comment le faire ! Et si vous trouvez ca déjà trop dur, alors je ne vous laisse même pas imaginer ce que ce serait si vous vous mettiez à Direct3D.


Vu dans ... http://www-evasion.imag.fr/Membres/Antoine.Bouthors/teaching/opengl/opengl2glut.html

Initialiser OpenGL avec GLUT

GLUT, quoi qu'est-ce ? Et bien c'est une librairie pour OpenGL (GL utility toolkit) destinée à faciliter la vie du programmeur. Non, elle ne vous fait pas la bouffe ni ne vous torche à votre place, mais elle sait quand même créer une fenêtre dans n'importe quel environnement de n'importe quel OS et bien plus, comme vous le verrez bientôt. Elle a été en fait désignée pour permettre une portabilité à 100% du code. Hé oui ! Vous avez bien lu ! 100% ! Ca veut dire quel que soit l'OS, tous vos programmes OpenGL seront parfaitement compilables sans aucune modifications à apporter ! C'est-y pas génial, ca ?
Mais alors pourquoi j'ai pas commencé par faire un tutorial glut ? Ben d'abord par ce que j'ai appris sans, et qu'ensuite je trouve plus pratique de développer sans glut pour ce qui est du débugage et des test, parce qu'on a beaucoup plus d'options et de trucs comme ça. Mine de rien, ca permet quand même d'appréhender les réactions de chaque machine à OpenGL, de voir les différences entres les cartes 3D etc... Mais glut a lui aussi plein d'avantages : d'abord on peut faire de tout petits programmes tous cons qui tiennent en quelques lignes et peuvent faire des effets excellent, et puis glut ne se contente pas de gérer le graphisme : on peut aussi grâce à glut gérer la souris, le clavier... C'est vraiment excellent !

Après cette brève introduction, passons à la partie code. Je vais moins commenter le code que dans le tutorial sur OpenGL pour win, parce que les remarque sont les mêmes. Donc je vous conseille de lire rapidement le tutorial sus-cité (surtout la 2e partie) afin de saisir les 2 ou 3 concepts de base avant d'aborder celui-ci. Et si vous avez déjà réussi à utiliser OpenGL avec windows, alors ce tutorial-ci sera une partie de plaisir. Dans les #include, on uniquement glut.h. Remarquez qu'ici il n'y a pas de windows.h : c'est un code tout à fait portable. Il n'y a pas non plus gl.h ou glu.h mais ceux-ci sont déjà inclus par glut.h normalement. Mais tout dépend des compilateurs et des systèmes : certains permettent d'inclure les 3, d'autres se contentent de glut.h. Mais pas de panique : toutes les fonctions gl et glu sonnt accessibles dès lors que vous incluez glut.h.

#include <gl/glut.h>

La différence entre les différentes plateformes se fera en fait au moment de la compilation : sous windows, vous incluerez glut32.lib, opengl32.lib et glu32.lib dans vos liens, tandis que sous Unix vous ferez "cc prog.c -o Prog -lX11 -lglut -lGL etc..."
Sous Visual C++, vous n'aurez donc plus à créer un Win32 Apllication Project, avec WinMain et tout le toutim, mais juste un Win32Console Application Project, c'est-à-dire un prog 32 bits qui sait utiliser les dll de win.

Et bien passons maintenant au main() (ahhh, enfin un main normal !), qui va d'ailleurs être plutôt dans le genre

int main( int argc, char *argv[ ], char *envp[ ] )

Si vous n'avez jamais vu cette syntaxe, sachez qu'elle est tout à fait conforme au C ANSI, et qu'elle permet juste de récupérer les arguments de ligne de commande et les variables d'environnement.
Commencons par remplir ce main() avec les fonctions d'initialistation de glut :

int WindowName;
int main( int argc, char *argv[ ], char *envp[ ] )
{ |
 | glutInit(&argc, argv);
 | glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
 | glutInitWindowSize(640,480); | //Optionnel
 | WindowName = glutCreateWindow("Ma première fenêtre OpenGL !");
 | glutFullScreen(); | //Optionnel
 | return 0;
} |
  • glutInit() a besoin des arguments de ligne de commande, donc on lui donne.
  • On transmet à glutInitDisplayMode() les options qu'on veut : ici, j'ai choisi un mode RGBA (32 bits), un double-buffering, et un Z-buffer à titre d'exemple.
  • glutInitWindowSize() et glutInitWindowPos() sont des fonctions qui permettent de spécifier la taille et la postition des fenêtres qui vont être créées.
  • Enfin, on crée notre fenêtre, en transmettant juste le titre, en on récupère un nom unique pour cette fenêtre, qui servira à la redimensionner, la fermer... Donc mieux vaut l'enregistrer en tant que variable globale.

Et là, vous pouvez d'ores et déjà lancer votre programme : ça crée une fenêtre OpenGL en plein écran et tout et tout... et ca la ferme aussitôt ! Hé oui, on a pas encore vu comment gérer les évènements comme on le faisait avec WM_PAINT et toutes ces autres conneries. Et ben on va le faire maintenant : vous vous rapellez qu'à réception du message WM_SIZE, on appelait notre fonction Reshape(), qui remettait les paramètres OpenGL aux goûts de la fenêtre. Et ben là, c'est tout pareil : y'a juste à écrire dans le main() après les quelques initialisations

glutReshapeFunc(Reshape);

Avec notre fonction Reshape() définie de la sorte :

void Reshape(int width, int height)
{
glViewport(0,0,width,height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(
45,
float(width)/float(height),
0.1,
100
); 		//Pour les explications, lire le tutorial sur OGL et win
glMatrixMode(GL_MODELVIEW); //Optionnel
}

Et de même, on appelait Draw() à réception du message WM_PAINT. Ici, c'est la même chose que pour Reshape() : on crée notre fonction Draw() qui va contenir toutes les fonctions de dessin, et on initialise glut avec

glutDisplayFunc(Draw);

A titre d'exemple, la fonction Draw() peut être quelque chose dans ce genre (référez-vous au tutorial OGL+Windows 2e partie pour toutes les explications sur InitGL(), Draw() et Reshape() ) :

void Draw()
{
} glClear
(
GL_COLOR_BUFFER_BIT |
GL_DEPTH_BUFFER_BIT
); 		//Efface le frame buffer et le Z-buffer
glMatrixMode(GL_MODELVIEW); | //Choisit la matrice MODELVIEW
glLoadIdentity(); | //Réinitialise la matrice
gluLookAt(0,0,-10,0,0,0,0,1,0);
glBegin(GL_TRIANGLES);

glVertex2i(0,1);
glVertex2i(-1,0);
glVertex2i(1,0);
glEnd(); | //Pour les explications, lire le tutorial sur OGL et win

glutSwapBuffers();

 //Attention : pas SwapBuffers(DC) !

glutPostRedisplay();

 //Demande de recalculer la scène

Remarquez que cette fonction diffère de la version "sans glut" par glutSwapBuffers(). Donc si vous voyez SwapBuffers(DC) dans les prochains tuts, ne vous étonnez pas et remplacez-le par glutSwapBuffers().
Attention également à glutPostRedisplay() : en effet, Draw() n'est appelée que lorsque glut pense que la fenêtre doit être rafraîchie (donc lorqu'on change de fenêtre, lorsqu'elle est réduite...). Pour demander à glut de refaire le rendu, il faut donc appeler glutPostRedisplay(), sinon votre fenêtre ne sera pas rafraîchie régulièrement.

Ensuite, après avoir initialisé glut, vous pouvez initialiser OpenGL lui-même en vous faisant une petite fonction InitGL contenant tous les trucs nécessaires : glEnable(GL_DEPTH_TEST), glEnable(GL_LIGHTING) etc... Cela ne vous dit peut-être rien pour l'instant, mais vous en aurez besoin plus tard.
Enfin, lancez le gestionnaire glut, qui se chargera de lancer les fonctions Draw() et Reshape() aux moments voulus, grâce à glutMainLoop().

Pour résumer, voici la fonction main() complète :

int main( int argc, char *argv[ ], char *envp[ ] )
{ |
 | glutInit(&argc, argv);
 | glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
 | glutInitWindowSize(640,480); | //Optionnel
 | WindowName = glutCreateWindow("Ma première fenêtre OpenGL !");
 | glutFullScreen(); | //Optionnel
 | glutReshapeFunc(Reshape);
 | glutDisplayFunc(Draw);
 | InitGL();
 | glutMainLoop();
 | return 0;
} |

Ici notre fonction InitGL() ne contient absolument rien, mais nous nous en servirons plus tard. Notez que glutMainLoop() ne s'arrête jamais : on est obligé de sortir du programme par un exit(), ou de fermer la fenêtre manuellement.

Le programme présenté ici donnera quelque chose de ce genre (là c'est sans fullscreen) :

Et voilà, c'est fini ! Vous pouvez maintenant garder cet exemple comme un canevas sur lequel vous n'aurez plus qu'à tisser votre code (c'est beau, non ?). Dans les prochains tutorials, je ferais toujours référence à Draw(), InitGL() et Reshape(), donc vous devrez être censés savoir ce que c'est. En plus, ces fonctions se retrouvent également dans la partie "sans glut" : comme ça je n'aurais pas à expliquer les choses de 2 façons différentes.


Vu dans ... http://www-evasion.imag.fr/Membres/Antoine.Bouthors/teaching/opengl/opengl3.html

OpenGL : les bases

Ce troisi�me volet s'inscrit directement dans la continuite du second, et il sera le meme que vous utilisiez glut ou non. Nous allons etudier les primitives et les matrices en nous appuyant sur le tutorial precedent. Reprenez donc le projet que nous avions commence, et qui affichait un magnifique triangle blanc.

Bon, on va commencer par le dessin en OpenGL. Pour dessiner, c'est hyper simple, et on peut faire n'importe quoi. Comme vous le savez serement si vous avez legerement touche e la 3D, TOUS les objet sont en fait une association d'elements de base : ce sont des faces orgranisees dans un certain ordre dans l'espace, qui representent l'objet. Chaque face peut etre deconposee en une serie de facettes triangulaires.

Ainsi, un rectangle est en fait constitue de deux triangles consecutifs :

Ces triangles sont eux-meme composes de 3 cotes, ou 3 lignes, et de 3 points (ou vertex) qui definissent les extremites de ces segments. Ces elements (points, lignes, triangles) sont aussi appeles primitives. OpenGL est base sur le principe des primitives, c'est-e-dire que pour dessiner un objet, il faut dessiner toutes ses primitives (ca parait logique). Et pour cela, on utilise des fonctions tres simples d'utilistation et permettant des dessins tres pousses, puisque grece e ces fonctions, on peut dessiner quasiment tout.
Passons maintenant e la pratique : comment dessiner un element primitif ? He bien vous l'aurez serement devine en lisant le code d'exemple du triangle : on appelle glBegin() avec comme parametre l'element, on lui envoie les coordonnees des vecteurs-cles via glVertex(), et on tremine avec glEnd(). Ultra simple, non ?
Notez que vous ne verrez rien si vous n'applez pas SwapBuffers(DC). Pourquoi ? Parce qu'on utilise une technique appelee double-buffering, qui consiste e afficher une image pendant que l'on calcule l'autre, et lorsque celle-ci est terminee, on echange les deux. Donc si on enleve SwapBuffers(DC), on verra toujours un ecran vide, tandis qu'on dessinera dans un ecran non visible. Si vous travaillez en mode simple-buffering (changez l'option dans SetupPixelFormat() pour les windows-lover, et dans glutInit() pour les glut-lover), il faut remplacer SwapBuffers() par glFlush(). Mais c'est stupide de n'utiliser qu'un seul buffer, car la construction de l'image est alors visible, ce qui provoque un effet de scintillement.
Voyons maintenant ce que l'on peut dessiner e part des triangles :

Parametre transmis e glBegin | Description | Exemple
GL_POINTS | Dessine un point pour chaque vertex transmis. |
GL_LINES | Dessine des lignes. Le point n defini le debut d'une ligne et le point n+1 la fin de la ligne. |
GL_LINE_STRIP | Dessine un groupe de lignes connectees, formant une chaine partant du premier vertex et s'arretant au deriner. |
GL_LINE_LOOP | Meme chose que GL_LINE_STRIP, mais en revenant au premier vertex e la fin. |
GL_TRIANGLES | Chaque triplet de vertex constitue un triangle |
GL_TRIANGLE_STRIP | Un triangle est defini pour chaque vertex presente apres les deux premiers |
GL_TRIANGLE_FAN | Meme chose que GL_TRIANGLE_STRIP, sauf que le premier vertex de chaque triangle est toujours le ne1 |
GL_QUADS | Chaque quadruplet de vertex definit un quadrilatere (compose de 2 triangles) |
GL_QUAD_STRIP | Chaque couple de vertex presente apres la premiere paire definit un quadrilatere (attention e l'ordre des points) |
GL_POLYGON | Dessine un polygone convexe |

Remarque : les lignes rouges et les numeros sur les exemples servent seulement e montrer comment sont dessines les primitives, mais ne sont pas visibles en realite.

Tout-e-l'heure, j'ai parle de glVertex(), et pourtant dans mon exemple il y a ecrit glVertex2d(). Pourquoi ? He bien parce qu'en fait il y a une multitude de fonctions pour dessiner des sommets. En fait, on appelle toujours la fonction glVertexxy[v](), oe :

  • x definit le nombre de dimension, de 2 e 4. Lorsqu'il y a 2 dimensions, on definit x et y, et z est par defaut e 0. Pourquoi aller jusqu'e 4 dimensions alors qu'on fait de la 3D ? C'est e cause des coordonnees homogenes. C'est un concept mathematique qui permet d'effectuer toutes les transformations 3D necessaires simplement avec des matrices 4x4 et des vecteurs 4D. En pratique, vous n'avez pas e toucher e cette 4e dimension, elle prend normalement toujours la valeur de 1. Si vous voulez en savoir plus, allez faire un tour dans le redbook ou sur des sites de maths.
  • y definit le type des parametres : 'i' pour int, 's' pour short, 'f' pour float, et 'd' pour double.
  • Lorsque l'on rajoute 'v', on passe comme parametre un pointeur sur les coordonnees plutet que les coordonnees elles-meme

Vous vous servirez en fait principalement de GL_TRIANGLES et GL_QUADS. Mais le nous allons nous servir de GL_LINES. En effet, maintenant que vous savez dessiner les primitives (donc que vous savez tout dessiner), nous allons etudier les transformations de base en OpenGL. Et pour cela, le plus simple est de dessiner un repere tridimensionnel. Votre fonction Draw() doit donc etre :

void Draw()
{ |
 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 | glMatrixMode(GL_MODELVIEW);
 | glBegin(GL_LINES);
 | glVertex2i(0,0);glVertex2i(0,1);
 | glVertex2i(0,0);glVertex2i(1,0);
 | glVertex2i(0,0);glVertex3i(0,0,1);
 | glEnd();
 | SwapBuffers(DC); | // glutSwapBuffers(); pour glut
 | glutPostRedisplay(); | // Uniquement pour GLUT
} |

Mais si vous lancez le programme tel quel, vous ne verrez rien. Pourquoi ? Parce qu'au depart, nous sommes place en (0,0,0) et nous regardons vers le point (0,0,-1). Or notre objet est situe en (0,0,0), donc nous ne verrons pas grand chose. Pour changer cela, il suffit d'inserer avant glBegin() :

gluLookAt(3,2,3,0,0,0,0,1,0);

gluLookAt() est une fonction tres simple et puissante permettant de definir un point de vue. Les 3 premiers parametres definissent les coordonnees du point de vue, les 3 suivant l'endroit oe il regarde, et les 3 derniers un vecteur qui dit oe est le haut de la camera. gluLookAt() effectue les operations necessaires sur la matrice active afin que le repere soit bien oriente. Nous devons donc activer nous-meme la matrice ModelView grece e glMatrixMode().

Vous pouvez maintenant lancer votre programme, et vous verrez une fenetre de ce genre (sans les textes, bien ser) :

Nous allons maintenant utiliser ce repere pour etudier les transformations, au nombre de 3. Declarez d'abord une variable globale double a=0; et commencons.

Les prinicpes fondamentaux :

  • Lorsque vous avez effectue une transformation, tous les objets dessines apres cette transformation sont affectes, tant que glLoadIdentity() n'est pas appele (auquel cas les objets ne seront plus transformes) ;
  • Les modifications affectent directement le repere de coordonnees et se basent sur le repere de coordonnees. Le axes utilises sont donc toujours les axes locaux. C'est-e-dire que si vous avez fait une rotation autour de l'axe X, et que vous voulez faire une rotation autour de l'axe Y, cette rotation s'effectuera autour de l'axe modifie par la premiere rotation (vous verrez un exemple concret tout-e-l'heure) ;
  • Un objet dessine ne peut plus etre modifie.

L'homotetie ( glScalef / glScaled ) :
Il s'agit d'un agrandissement ou une reduction par rapport au centre du repere de coordonnees. Si vous etes un habitue de 3DS, cette fonction revient e faire un Non-Uniform Scaling sur tous les objets dessines apres. Exemple : inserez avant le glBegin() de Draw() les lignes suivantes (sans oublier d'inclure math.h pour les fonctions trigo) :

glScaled(1.-cos(a)/3.,1.+cos(a)/3.,1.+cos(a)/3.);
a+=.1;

Et vous verrez une belle animation dans ce genre :

Le repere est agrandi ou reduit selon certains axes. Comme je l'ai dit precedemment, toutes les transformations qui suivent seront basees sur le nouveau repere, tant que celui-ci n'aura pas ete reinitialise par glLoadIdentity(). Donc vous translatez un objet apres avoir effectue une reduction par glScale(), le deplacement sera plus court, puisque le repere aura diminue.

la translation ( glTranslated / glTranslatef ) :
C'est en fait un deplacement selon le vecteur passe en parametre. Le meilleur moyen de l'expliquer est de fournir un exemple. e la place du glScaled, ecrivez :

glTranslated(cos(a),0,sin(a));

Et ca donne :

Je ne le repeterais jamais assez : les transformations affectent aussi le systeme de coordonnees. Donc si vous avez effectue une translation, et qu'ensuite vous vouliez faire une homotetie, le centre de cette homotetie aura lui aussi ete deplace.

la rotation ( glRotated / glRotatef ) :
Elle permet d'effectuer une rotation autour de n'importe quel axe. On lui passe comme parametre l'angle en degre et les coordonnees x,y et z du vecteur de rotation. La rotation fait tout tourner, y compri le repere lui-meme (comme les autres transformations d'ailleurs). Donc si vous faites une premiere rotation selon un vecteur v1, et que vous voulez faire une deuxieme rotation selon un autre vecteur v2, n'oubliez pas que v2 a lui aussi subi la premiere rotation ! Un petit exemple ?

glRotated(a*180/3.14,1,1,1);

donne :

Vous pouvez bien ser combiner les transformations. Essayez par exemple les 3 en meme temps, c'est assez sympa.

Et voile ! Vous savez tout sur le dessin et les transformation et OpenGL. Vous pouvez donc maintenant tout dessiner ! Bon, OK, c'est lourd de dessiner une mobylette e la mano, mais en fait glu vous permet d'y arriver plus facilement : elle fournit des primitives moins primitives, avec gluSphere, gluDisk, gluCylinder, gluPartialDisk... Etudiez ces fonctions avec F1, et amusez-vous e dessiner de petits objets et e tourner autour. Je vous rapelle que le meilleur moyen d'apprendre est encore d'apprendre par soi-meme. Donc entraenez-vous e manier les transformations et les primitives jusqu'e ce que vous vous soyez bien familiarises avec.

La prochaine fois, nous verrons le coloriage, alors preparez vos crayons de couleurs ;-)


Vu dans ... http://www-evasion.imag.fr/Membres/Antoine.Bouthors/teaching/opengl/opengl4.html

OpenGL : les couleurs

Au cas o� vous ne l'aviez pas encore remarqu�, tout ce qu'on a fait jusqu'� pr�sent, c'�tait tout blanc. Mais tout blanc alors. Tellement qu'en fait on ne pouvait pas distinguer 2 faces entre elles, ce qui m'a pouss� jusqu'ici � dessiner des lignes plut�t que des faces, et ca faisait un petit peu limit� pour ce que peu faire r�ellement la b�te. Donc maintenant on va pouvoir gr�ce � ce tutorial dessiner de beaux objets "solides" et bien visibles, et pas des petits cubes � la con en mode filaire.

Reperenez donc (comme par hasard) le pogramme du tutorial pr�c�dent, et modifiez Draw() de fa�on � n'avoir que le strict minimum :

void Draw()
{ |
 | glClear(GL_COLOR_BUFFER_BIT);
 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 | glMatrixMode(GL_MODELVIEW);
 | SwapBuffers(DC) | // glutSwapBuffers(); pour glut
 | glutPostRedisplay(); | //Uniquement pour GLUT
} |

Bon l� on n'a pas grand chose, mais on va dessiner un cube. Qui dit cube dit 6 faces rectangulaires. Donc hop c'est parti :

void Draw()
{

glClear(GL_COLOR_BUFFER_BIT);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
gluLookAt(5,5,5,0,0,0,0,1,0);
glBegin(GL_QUADS);

glVertex3i(1,1,1);
glVertex3i(1,-1,1);
glVertex3i(-1,-1,1);
glVertex3i(-1,1,1);

 //1 face

glVertex3i(1,1,-1);
glVertex3i(1,-1,-1);
glVertex3i(-1,-1,-1);
glVertex3i(-1,1,-1);

 //2 faces

glVertex3i(1,1,1);
glVertex3i(1,-1,1);
glVertex3i(1,-1,-1);
glVertex3i(1,1,-1);

 //3 faces

glVertex3i(-1,1,1);
glVertex3i(-1,-1,1);
glVertex3i(-1,-1,-1);
glVertex3i(-1,1,-1);

 //4 faces

glVertex3i(-1,1,-1);
glVertex3i(-1,1,1);
glVertex3i(1,1,1);
glVertex3i(1,1,-1);

 //5 faces

glVertex3i(-1,-1,-1);
glVertex3i(-1,-1,1);
glVertex3i(1,-1,1);
glVertex3i(1,-1,-1);

 //6 faces
glEnd();

SwapBuffers(DC)

 //glutSwapBuffers(); pour glut
glutPostRedisplay(); //Uniquement pour GLUT
}

Bon ok c'est lourd mais rien ne vous interdit de tout recopier � la main, copier/coller ca existe. Et puis de toute fa�on �a fait un bon entrainement.
Sinon pour l'instant rien de nouveau, on utilise ce qu'on a vu dans les tuts pr�c�dents. Et qu'est-ce que �a donne, �a ? Et ben on obtient un truc dans ce genre :

C'est vrai qu'on a un peu de mal � faire la distinction entre l'une ou l'autre face. C'est pour �a que j'ai choisi de mettre des couleurs aux faces. Nous en venons donc � : "comment qu'on va peinturlurer ce truc sans s'en foutre plein partout ?" H� bien gr�ce � cette merveilleuse fonction glColor() !

Comme pour glVertex(), glColor() va de glColor3b() (R,G,B sur un octet sign�) � glColor4usv() (R,G,B,Alpha sur un pointeur sur short non sign�) en passant par glColor3d() (R,G,B sur un double, de 0.0 � 1.0). C'est justement cette derni�re fonction que nous allons utiliser car pouvoir s�lectionner l'intensit� de la couleur entre 0 et 1 est bien pratique. Pour les codages sur des valeurs enti�res, tout l'intervalle disponible est utilis� (par exemple en ub - unsigned byte -, les couleurs iront de 0 � 255).
Bien. Comment se sert-on de glColor() maintenant ? En fait, quand on appelle cette fonction, elle va changer la couleur des vertices (sommets) que l'on dessinera apr�s l'appel de la fonction, selon le principe de la machine d'�tat. Donc puisqu'on veut une couleur unie par face, on va appeler glColor() 6 fois, une fois avant chaque face :

void Draw()
{

glClear(GL_COLOR_BUFFER_BIT);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
gluLookAt(5,5,5,0,0,0,0,1,0);
glBegin(GL_QUADS);

glColor3d(1,0,0);
glVertex3i(1,1,1);
glVertex3i(1,-1,1);
glVertex3i(-1,-1,1);
glVertex3i(-1,1,1);

 //1 face

glColor3d(0,1,0);
glVertex3i(1,1,-1);
glVertex3i(1,-1,-1);
glVertex3i(-1,-1,-1);
glVertex3i(-1,1,-1);

 //2 faces

glColor3d(0,0,1);
glVertex3i(1,1,1);
glVertex3i(1,-1,1);
glVertex3i(1,-1,-1);
glVertex3i(1,1,-1);

 //3 faces

glColor3d(0,1,1);
glVertex3i(-1,1,1);
glVertex3i(-1,-1,1);
glVertex3i(-1,-1,-1);
glVertex3i(-1,1,-1);

 //4 faces

glColor3d(1,1,0);
glVertex3i(-1,1,-1);
glVertex3i(-1,1,1);
glVertex3i(1,1,1);
glVertex3i(1,1,-1);

 //5 faces

glColor3d(1,0,1);
glVertex3i(-1,-1,-1);
glVertex3i(-1,-1,1);
glVertex3i(1,-1,1);
glVertex3i(1,-1,-1);

 //6 faces
glEnd();

SwapBuffers(DC)

 //glutSwapBuffers(); pour glut
glutPostRedisplay(); //Uniquement pour GLUT
}

Et hop c'est fini et voil� le r�sultat :
Avouez que c'est tout de m�me plus joli. Bon ok mes couleurs sont � chier, mais l� au moins on voit la diff�rence entre les faces; et puis de toute fa�on si vous �tes pas content vous n'avez qu'� changer les couleurs, c'est vous qui choisissez apr�s tout.
Remarquez que l'on a appel� glColor() entre glBegin() et glEnd(), et cela n'a pos� aucun probl�me. Certaines fonctions le peuvent et d'autres non, selon leur action.

Ah non ! J'oubliais ! Si vous lancez comme �a, �a marchera, mais vous verrez vraiment un cube bizarre. En effet, nous n'avons pas dit � OpenGL de ne pas afficher les faces cach�es. Il va donc tout dessiner dans l'ordre o� on lui a donn� les faces, sans se pr�occuper s'il y avait quelque chose devant ou pas. Il faut donc activer le test de profondeur. Pour cela, tapez
glEnable(GL_DEPTH_TEST);
dans InitGL(). Et rajoutez aussi
glEnable(GL_COLOR_MATERIAL);
tant que vous y �tes, pour �tre bien s�r que le coloriage est activ�.

Mais au fait, quand on regarde un cube tout blanc (ou un lapin ou un kangourou si vous pr�f�rez) dans la r�alit�, on voit quand m�me bien les ar�tes, m�me si les faces sont immacul�es. Ben oui, parce qu'en g�n�ral, les faces ne sont pas �clair�es de la m�me fa�on. Or ici on utilise l'�clairage par d�faut tout � fait pourri je le con�oit, mais qui au moins nous permet d'y voir quelque chose. C'est un �clairage dit "ambient", c'est-�-dire que la lumi�re est exactement la m�me en tout point (un peu genre lumi�re "ambiente" dans 3DS). C'est pour �a que toutes les faces ont exactement la m�me couleur.
Mais on ne va pas aborder les lumi�res maintenant, plut�t dans le prochain tutorial. Pourquoi je vous parle de �a alors ? D'abord pour que vous compreniez mieux (si qq chose vous tracasse encore, n'h�sitez pas � me le dire), et ensuite parce que glColor() n'est pas sans liens avec l'�clairage. En effet, comme dans 3DS, chaque objet a plusieurs couleurs : couleur ambiente (celle qu'a l'objet lorsqu'aucune lumi�re ne le frappe), couleur diffuse (couleur que renvoie l'objet lorsqu'il est �clair�), couleur sp�culaire (couleur que renvoie l'objet lorsqu'il est frapp� directement par la lumi�re) et couleur d'�mission (couleur qu'�met l'objet, comme un trux fluorescent par exemple). Il faut bien faire la diff�rence entre diffuse et sp�culaire : une sph�re m�tallique aura par exemple une couleur diffuse grise, et une couleur sp�culaire tr�s blanche, qui correspondra au point tr�s lumineux � l'endroit o� la lumi�re vient se r�fl�chir. Une image illustre mieux qu'un long discours :

Ici la couleur ambiente est bleut�e. Elle ne se voit pas beaucoup, mais elle est tout de m�me pr�sente. La couleur diffuse est grise, mais on voit que l'ambiente influe aussi dessus, ce qui la rend gris-bleu. Quand � la sp�culaire, elle est carr�ment blanche et l'ambiente n'a quasiment aucune influence dessus. Cette image a �t� r�alis�e avec 3DS, mais vous verrez qu'on peut en faire d'aussi belles avec OpenGL dans le prochain tutorial.

Revenons � nos moutons. Ici nous modifions la couleur par d�faut, qui est GL_AMBIENT_AND_DIFFUSE. Nous ne changeons donc pas la couleur sp�culaire. Pour choisir la couleur modifi�e par glColor(), il faut appeler d'abord glColorMaterial(face,mode). face d�signe les faces qui seront affect�es : GL_FRONT pour les face avant, GL_BACK pour les faces arri�res et GL_FRONT_AND_BACK pour les 2. Comme nous n'avons pas encore vu l'orientation des faces, choisissez GL_FRONT_AND_BACK (d�faut). mode d�finit lui la couleur � modifier : GL_EMISSION, GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR, et GL_AMBIENT_AND_DIFFUSE (d�faut). Notez que cette fonction ne peut pas �tre appel�e entre glBegin() et glEnd() (c'est une source fr�quente d'erreurs).
Essayez avant le glBegin() de s�lectionner une couleur diff�rente et vous verrez qu'en fait le r�sultat est le m�me quel que soit la couleur choisie. C'est parce que nous sommes toujours avec l'�clairage par d�faut, qui permet surtout de d�couvrir et d'apprendre les fonctions basiques de dessin en �tant s�r de voir qq chose. Nous devrons donc �tudier en d�tail l'�clairage dans un prochain tutorial.

Mais il a autre chose que je n'ai pas compl�tement �clairci. En effet, si vous �tes attentif, vous avez peut-�tre remarqu� que j'ai dit plus haut que glColor() d�finissait la couleur des sommets qui allaient �tre dessin�s apr�s. Donc a priori, on peut essayer de d�finir des couleurs diff�rentes pour chaque sommet d'une face. Qu'est-ce donc que �a va donner, �a ? Eh ben le meilleur moyen de le savoir, c'est tout simplement d'essayer...

void Draw()
{

glClear(GL_COLOR_BUFFER_BIT);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
gluLookAt(5,5,5,0,0,0,0,1,0);
glBegin(GL_QUADS);

glColor3d(1,0,0);glVertex3i(1,1,1);
glColor3d(0,1,0);glVertex3i(1,-1,1);
glColor3d(0,0,1);glVertex3i(-1,-1,1);
glColor3d(1,0,1);glVertex3i(-1,1,1);

//1 face

glColor3d(1,1,0);glVertex3i(1,1,-1);
glColor3d(0,1,1);glVertex3i(1,-1,-1);
glColor3d(1,0,0);glVertex3i(-1,-1,-1);
glColor3d(1,1,1);glVertex3i(-1,1,-1);

//2 faces

glColor3d(1,0,0);glVertex3i(1,1,1);
glColor3d(0,1,0);glVertex3i(1,-1,1);
glColor3d(0,1,1);glVertex3i(1,-1,-1);
glColor3d(1,1,0);glVertex3i(1,1,-1);

//3 faces

glColor3d(1,0,1);glVertex3i(-1,1,1);
glColor3d(0,0,1);glVertex3i(-1,-1,1);
glColor3d(1,0,0);glVertex3i(-1,-1,-1);
glColor3d(1,1,1);glVertex3i(-1,1,-1);

//4 faces

glColor3d(1,1,1);glVertex3i(-1,1,-1);
glColor3d(1,0,1);glVertex3i(-1,1,1);
glColor3d(1,0,0);glVertex3i(1,1,1);
glColor3d(1,1,0);glVertex3i(1,1,-1);

//5 faces

glColor3d(1,0,0);glVertex3i(-1,-1,-1);
glColor3d(0,0,1);glVertex3i(-1,-1,1);
glColor3d(0,1,0);glVertex3i(1,-1,1);
glColor3d(0,1,1);glVertex3i(1,-1,-1);

//6 faces
glEnd();

SwapBuffers(DC)

 //glutSwapBuffers(); pour glut
glutPostRedisplay(); //Uniquement pour GLUT
}

Ok c'est peut-�tre un peu lourd, mais au moins c'est clair : chaque sommet a une couleur diff�rente (j'ai mis les sommets (1,1,1) et (-1,-1,-1) tous les deux en rouge pour ne pas utiliser la couleur noire). Et voil� ce que �a donne. Comme vous pouvez le voir, la couleur d'un point sur la face est interpol�e entre chaque sommet, et �a donne des r�sultats plut�t sympas. On voit un peu mieux les ar�tes, on a m�me l'impression qu'elles sont �clair�es, mais en fait ce n'est qu'une illusion.

Gr�ce � ce tutorial en tout cas nous avons d�couvert que :

  • On peut modifier la couleur active avec glColor();
  • On peut choisir la couleur active avec glColorMaterial();
  • On peut activer certaines options (test de profondeur, coloriage...) gr�ce � glEnable() - utilisez l'aide de votre compilateur pour d�couvrir tout ce que cette fonction rec�le ;
  • Un objet est compos� de diff�rentes couleurs : �mission, ambiente, diffuse et sp�culaire.
  • Une face peut avoir des sommets de couleurs diff�rentes

Un "truc" de graphiste (je suis pas graphiste mais bon c'est toujours un conseil) : �vitez le plus possible de modifier la couleur ambiente d'un objet car elle affecte aussi les autres couleurs, et donc demande des r�glages tr�s subtils, voire impossibles. Laissez-la donc noire et utilisez plut�t les couleurs diffuse et sp�culaire qui sont plus pratiques et rapides � r�gler.


Vu dans ... http://www-evasion.imag.fr/Membres/Antoine.Bouthors/teaching/opengl/opengl5.html

OpenGL : les lumi�res

1. On d�marre

"Que la lumi�re soit."
Moi ;-)

Nous allons maintenant discuter des sources de lumi�re. C'est un sujet assez �tendu et un peu plus compliqu� que ce qu'on a vu pr�c�dement. Et comme il y a beaucoup � dire, nous n'allons voir dans une premi�re partie que les g�n�ralit�s et les sources omnidirectionnelles (les 'omni ' pour les fans 3DS). Nous verrons les spots dans le deuxi�me chapitre. Je vous pr�viens tout de suite : il y a beaucoup plus de bla-bla que dans les autres, parce qu'il y a beaucoup � expliquer, m�me si certains connaissent d�j� ces notions.

Comme d'habitude, reprenons notre programme de base avec la fonction Draw() r�duite au minimum :

void Draw()
{ |
 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 | SwapBuffers(DC) | // glutSwapBuffers(); pour glut
 | glutPostRedisplay(); | // Uniquement pour GLUT
} |

Le meilleur objet pour illustrer l'exemple des lumi�res est la sph�re. Nous aurions pu dessiner une sph�re face par face comme pour le cube du tutorial sur les couleurs, mais bon on n'est pas maso non plus. Alors on va utiliser une proc�dure bien pratique de glut.h : glutSolidSphere(). Elle prend en arguments le rayon de la sph�re, le nombre de parall�les, et le nombre de m�ridiens. Nous allons illuminer cette sph�re par une source de lumi�re ponctuelle situ�e au m�me endroit que nous. Mais pour que la sph�re soit �clair�e, il faut d'abord faire quelques initialisations :

void InitGL()
{ |
 | glEnable(GL_DEPTH_TEST); | // Active le test de profondeur
 | glEnable(GL_LIGHTING); | // Active l'�clairage
 | glEnable(GL_LIGHT0); | // Allume la lumi�re n�1
} |

Le code parle de lui-m�me : glEnable(GL_DEPTH_TEST) active le test de profondeur, c'est-�-dire que si un point d'une face se situe derri�re une autre face, il n'est pas dessin�. glEnable(GL_LIGHTING)active la gestion de lumi�res, et glEnable(GL_LIGHT0)allume la lumi�re n�1. Vous aurez bien s�r compris qu'il suffit de faire glEnable(GL_LIGHT1)pour allumer la lumi�re n�2 etc. Vous disposez d'au moins 8 sources de lumi�res, le nombre maximum (d�finit par la constant GL_MAX_LIGHT) �tant limit� par votre syst�me. Maintenant que nous avons allum� la lumi�re, nous pouvons donc dessiner notre sph�re :

void Draw()
{ |
 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 | glMatrixMode(GL_MODELVIEW);
 | glTranslated(0,0,-5); |
 | glutSolidSphere(1,30,30); |
 | SwapBuffers(DC) | // glutSwapBuffers(); pour glut
 | glutPostRedisplay(); | // Uniquement pour GLUT
} |

2. Facteur de brillance et couleur sp�culaire
Et voil� ce que �a donne. Bon OK en 256 couleurs c'est pas super mais on voit quand m�me bien que c'est une sph�re, alors que sans lumi�re �a aurait fait juste un disque tout blanc et tout pas beau. Maintenant plongons-nous encore plus dans les lumi�res : nous avons vu dans le tutorial pr�c�dent que chaque objet �tait constitu� d'un mat�riau, avec des propri�t�s comme la couleurs ambiente, diffuse etc... Ces param�tres d�finissent comment est r�fl�chie la lumi�re sur les sommets pour chaque composante (0 : composante totalement absorb�e par l'objet ; 1 : composante totalement r�fl�chie par l'objet). Mais un mat�riau dispose aussi d'une autre propri�t� : la brillance. Ce param�tre d�finit la taille de la tache sp�culaire. Une valeur de 0 d�finit une tache sp�culaire la plus grande possible, et une valeur de 128 d�finit une tache toute petite. La valeur par d�faut est de 0, donc normalement la partie illumin�e devrait �tre de la couleur sp�culaire, sans d�grad�. Or ici c'est bien la couleurs diffuse que l'on voit, et non la sp�culaire. Pourquoi ? Et bien tout simplement parce que la couleur sp�culaire par d�faut du mat�riau est nulle. Nous allons donc la changer. Pour cela, ce n'est pas dur, nous l'avons vu dans le tutorial pr�c�dent, il suffit d'appeler glColor() en ayant pr�alablement choisi GL_SPECULAR avec glColorMaterial(). Mais il y a un autre moyen de la modifier : c'est glMaterial(). En fait, glColor() est une alternative � glMaterial() pour modifier les couleurs d'un mat�riau. Si GL_COLOR_MATERIAL est d�sactiv� (par d�faut), on utilise glMaterial() pour changer les couleurs. S'il est activ�, alors on utilisera glColor(), et glMaterial() n'aura plus d'effet sur les couleurs (par contre il en aura sur les autres param�tres des mat�riaux). glMaterial() a l'avantage de pouvoir �tre appel� entre glBegin() et glEnd() (glColor() aussi, mais pas glColorMaterial() ), mais il est quand m�me plus lent que glColor(), c'est pourquoi il est conseill� d'utiliser glColor() plut�t que glMaterial() pour modifier les couleurs d'un mat�riau.
Bon, bref, si on met la couleur sp�culaire de notre sph�re � [image] (1,1,1) comme elle le devrait, on obtiendrait l'image que vous voyez � droite. On a plut�t l'impression d'�tre revenu au point de d�part : l'objet est tout blanc, on ne distingue rien etc etc... Mais je vous l'ai dit : c'est parce que l'indice de brillance (shininess) est � 0, ce qui fait que la tache sp�culaire est tellement grande qu'elle a bouff� la partie diffuse. Il suffit donc de modifier ce param�tre gr�ce � glMaterial() pour tout de suite avoir un aspect beaucoup plus r�aliste. Si on r�gle ce param�tre � 100 (sa valeur peut �tre comprise entre 0 et 128), on obtient quelque chose dans le genre de ce que vous pouvez voir � votre gauche. C'est quand m�me autrement plus joli. Mais pour ma part, je ne suis pas enti�rement satisfait. En effet, la source de lumi�re est au m�me endroit que la cam�ra, ce qui fait que toutes les partie visibles de l'objet sont �clair�es. Ceci serait plus beau si la source de lumi�re provenait d'un point dans l'espace. Et c'est l� que nous allons parler des lumi�res directionnelles (directionnal lights) et ponctuelles (positionnal lights).

3. Lumi�re directionnelle et lumi�re ponctuelle
Ici encore les adeptes de 3DS ou autres POV n'auront aucun mal � saisir ces notions, puisque ce sont les bases de tout modeleur 3D qui se respecte. Allons-y : une source de lumi�re ponctuelle est une source dont les rayons partent tous du centre de la source. Une source de lumi�re directionnelle est une source dont les rayons ont la m�me direction en tout point de l'espace, comme une source ponctuelle qui serait situ�e tellement loin de la sc�ne que les rayons pourraient �tre consid�r�s comme parall�les. L'exemple parfait de lumi�re directionnelle dans notre monde, qui est d'ailleurs cit� dans n'importe quel bouquin expliquant ce genre de trucs, est bien s�r le soleil : tous ses rayons vont dans la m�me direction, que l'on soit � Paris o� � Qu�bec, m�me si des milliers de km s�parent ces 2 villes. Voil� un petit sh�ma histoire de vous �claircir les id�es, plut�t que de vous embrouiller avec mes explications douteuses :

4. Position de la source

Voil�, l� je pense que vous comprenez ce que je veux dire. Nous allons donc modifier notre source de lumi�re pour qu'elle rayonne de biais par rapport � la sph�re. Mais au fait, quels sont les param�tres par d�faut de GL_LIGHT0 ? Et bien elle est directionnelle et rayonne le long de l'axe z, dans le sens des valeur n�gatives. Donc si on se place par exemple en (5,5,5) en regardant le point (0,0,0) avec gluLookAt(), on devrait voir une sph�re �clair�e de biais. Or, si vous faites le test, vous verrez exatcement la m�me image que lorsque vous �tiez en (0,0,-5). Pourquoi donc ? Et bien en fait la position des lumi�res est d�termin�e de la m�me fa�on que pour les primitives : avec la matrice modelview. Lorsque vous d�finissez la postition de la lumi�re, elle est multipli�e par la matrice modelview courante. Or la position par d�faut de la lumi�re est d�finie � l'initialisation et plus du tout apr�s. Donc s'il y a une modification de matrice apr�s la d�finition de la position de la lumi�re, cette derni�re n'est pas affect�e, tout comme une primitive.
Il faut donc red�finir la position de la lumi�re apr�s les modifications de matrice, comme pour tout objet, si vous voulez avoir une lumi�re stationnaire. Ca a l'air bizarre et compliqu�, mais en fait c'est plus pratique : vous pouvez effectuer des transformations aux sources de lumi�re comme vous voulez comme si elles �taient des objets normaux, et en plus si vous voulez une source de lumi�re relative au point de vue (comme par exemple une lampe de casque de mineur), il suffit de la d�finir avant toute modification de matrice. Voil� trois exemples-type pour illustrer ces longues explications :

Lumi�re fixe, point de vue mobile | Point de vue fixe, lumi�re mobile | Point de vue mobile, lumi�re relative au point de vue

Vous aurez devin� que le point jaune repr�sente la lumi�re et les lignes rouges et jaunes les axes X et Z. Voyons donc maintenant le code corespondant � chacun des exemples. Je ne rajoute pas les fonctions de dessin des lignes et du point, histoire de ne conserver que l'essentiel.

4.1 Premier exemple : lumi�re fixe
Il suffit d'effectuer en premier lieu les transformations, et ensuite de placer la lumi�re et les objets :

double a=0;
int LightPos[4] = {0,0,3,1};
int MatSpec [4] = {1,1,1,1};
void Draw()
{ |
 | glMaterialiv(GL_FRONT_AND_BACK,GL_SPECULAR,MatSpec);
 | glMateriali(GL_FRONT_AND_BACK,GL_SHININESS,100);
 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 | glMatrixMode(GL_MODELVIEW);
 | glLoadIdentity();
 | gluLookAt(0,6,6,0,0,0,0,1,0);
 | glRotated(a,0,1,0);
 | glLightiv(GL_LIGHT0,GL_POSITION,LightPos);
 | glutSolidSphere(1,50,50);
 | a=+1;
 | SwapBuffers(DC) | // glutSwapBuffers(); pour glut
 | glutPostRedisplay(); | // Uniquement pour GLUT
} |

On modifie d'abord les param�tres du mat�riau courant : couleur sp�culaire blanche et braillance 100 (remarquez que comme le mat�riau est toujours le m�me, on aurait pu mettre ces lignes dans InitGL() ).
Vous voyez ensuite que l'on modifie les param�tres d'une source de lumi�re par glLight{if}[v](). Il suffit de lui transmettre le nom de la lumi�re, le nom du param�tre que l'on veut changer, et sa nouvelle valeur. Ici encore, le postfixe 'i' signifie que l'on passe des entiers, 'f' que l'on passe des float, et 'v' que le troisi�me param�tre est un pointeur sur les nouvelles valeurs.
Voyons maintenant de plus pr�s ce que l'on transmet comme position : il ne s'agit pas comme on pourrait s'y attendre de trois valeurs x,y et z, mais de 4 valeurs. Les trois premi�res sont effectivement les coordonn�es cart�siennes, mais que vient foutre ici le 4e �l�ment ? Ben en fait c'est gr�ce � lui que vous allez d�terminer si une source est ponctuelle ou directionnelle (c'est vrai, quoi : je vous bassine avec �a depuis tout-�-l'heure, il fallait bien qu'il se trouve quelque part). Si w (c'est son nom) est � 0 comme dans l'exemple, alors la lumi�re est ponctuelle. Sinon elle est directionnelle et son sens part du point position transmis vers le point (0,0,0) (donc �vitez de cr�er une lumi�re directionnelle avec comme position le point (0,0,0) ). Essayez de changer la lumi�re de l'exemple en lumi�re directionnelle, pour voir.

4.2 Deuxi�me exemple : lumi�re mobile
L� non plus c'est pas compliqu� : il suffit de ne faire tourner que la lumi�re et non la sph�re, donc d'�crire :

double a=0;
int LightPos[4] = {0,0,3,1};
int MatSpec [4] = {1,1,1,1};
void Draw()
{ |
 | glMaterialiv(GL_FRONT_AND_BACK,GL_SPECULAR,MatSpec);
 | glMateriali(GL_FRONT_AND_BACK,GL_SHININESS,100);
 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 | glMatrixMode(GL_MODELVIEW);
 | glLoadIdentity();
 | gluLookAt(0,6,6,0,0,0,0,1,0);
 | glRotated(a,0,1,0);
 | glLightiv(GL_LIGHT0,GL_POSITION,LightPos);
 | glRotated(-a,0,1,0);
 | glutSolidSphere(1,50,50);
 | a=+1;
 | SwapBuffers(DC) | // glutSwapBuffers(); pour glut
 | glutPostRedisplay(); | // Uniquement pour GLUT
} |

Et voil�. Au lieu de faire glLoadIdentity() puis gluLookAt() avant de dessiner la sph�re, j'ai mis glRotated(-a,0,1,0), cequi annule la rotation, donc revient au m�me. Si vous testez cet exemple, vous ne verrez en fait aucune diff�rence par rapport au pr�c�dent, car vous n'avez pas de rep�re fixe. Le r�sultat sera le m�me : on a l'impression que la lumi�re tourne autour de la sph�re, car celle-ci est tellement parfaite qu'on ne distingue pas si elle tourne ou non. Par contre, si vous lui attribuez 5 m�ridiens et 5 parall�les au lieu de 50, l� vous verrez une diff�rence.

4.3 Troisi�me exemple : lumi�re relative au point de vue
Comme je l'ai �galement expliqu�, il suffit de d�finir la position de la lumi�re avant toute transformation pour que la position soit toujours la m�me par rapport � la cam�ra. Donc notre fonction Draw() sera comme ceci :

double a=0;
int LightPos[4] = {0,0,0,1};
int MatSpec [4] = {1,1,1,1};
void Draw()
{ |
 | glMaterialiv(GL_FRONT_AND_BACK,GL_SPECULAR,MatSpec);
 | glMateriali(GL_FRONT_AND_BACK,GL_SHININESS,100);
 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 | glMatrixMode(GL_MODELVIEW);
 | glLoadIdentity();
 | glLightiv(GL_LIGHT0,GL_POSITION,LightPos);
 | gluLookAt(0,6,6,0,0,0,0,1,0);
 | glRotated(a,0,1,0);
 | glutSolidSphere(1,50,50);
 | a=+1;
 | SwapBuffers(DC) | // glutSwapBuffers(); pour glut
 | glutPostRedisplay(); | // Uniquement pour GLUT
} |

Ici la lumi�re est ponctuelle et situ�e exactement au m�me endroit que l'oeil (remarquez que comme elle ne bouge jamais et ne se pr�occupe pas des transformations, on aurait pu mettre glLightiv() dans InitGL() ). Si vous conservez 50 m�ridiens et 50 parall�les, vous aurez du mal � voir la sph�re tourner. Ici encore, essayez 5 m�ridiens et 5 parall�les, vous y verrez plus clair.

5. Att�nuation

Bon, vous en avez d�j� pas mal vu jusqu'ici, mais il reste encore quelques points � voir. Et notemment l'att�nuation. En effet, il est rare de voir une lumi�re ponctuelle pouvant �clairer � une distance infinie. Si vous �tes capable de voir la Grande Ourse de chez vous, je doute que votre lampe de chevet puisse �clairer aussi loin que cette �toile. OpenGL vous permet d'ajouter des facteurs d'att�nuation � vos sources de lumi�re. Comme une lumi�re directionnelle est consid�r�e comme une lumi�re �tant � une distance infinie, l'att�nuation n'est pas valable pour ce genre de sources.
Vous disposez de 3 constantes d'att�nuation pour chaque lumi�re : GL_CONSTANT_ATTENUATION (appelons-la Kc), GL_LINEAR_ATTENUATION (Kl) et GL_QUADRATIC_ATTENUATION (Kq). Si d est la distance d'un point � la source de lumi�re, la quantit� de chaque composante de la lumi�re qui arrive � ce point est multipli�e par : 1 / (Kc + Kl*d + Kq*d�). Pour changer les constantes d'att�nuation il suffit d'appeler glLight(). Par exemple,
glLightf(GL_LIGHT0,GL_QUADRATIC_ATTENUATION,.01f);
Mettra � 0.01 la constante d'att�nuation Kq de la lumi�re GL_LIGHT0.
Par d�faut, on a Kc=1, Kl=0 et Kq=0, donc la lumi�re n'est pas att�nu�e. Si par exemple vous choisissez pour Kc, Kl et Kq des valeurs respective de 0.1, 0 et 0.01, vous pourrez obtenir quelque chose de ce genre :

Chaque sph�re a un rayon de 0.4 et l'espace entre 2 centres est de 1. Notez qu'il n'est pas possible d'att�nuer une seuke composante (rouge, bleue ou verte) et que toutes les couleurs de la lumi�re (ambiente, diffuse et sp�culaire) sont att�nu�es.

6. Couleur

Tiens, justement, qu'est-ce que c'est que �a, "les couleurs de la lumi�re" ? On connaissait d�j� les couleurs des mat�riaux, mais pas �a. Et bien, comme vous auriez pu vous en douter, chaque lumi�re a ses propres couleurs. En effet, un girophare est rouge ou bleu, une lampe � incandescence �met une lumi�re plut�t jaune, le soleil couchant lance ses ultimes rayons rouge-orang�s sur la verte colline... Bref, y'a pas que les objets qui ont des couleurs : les lumi�res aussi ont droit � la diff�rence. Et comme les objets, elles poss�dent chacune une couleur ambiente, une diffuse et une sp�culaire. Prenons l'exemple d'un plafonnier dans une pi�ce : les rayons rebondissent sur les murs et les objet recoivent une lumi�re dont la provenance est impossible � d�terminer tant la lumi�re a �t� dispers�e : c'est la composante ambiente de la lumi�re. La lumi�re diffuse est ce qu'on pourrait appeler la couleur r�elle de la source : c'est la couleur des rayons qui viennent directement frapper l'objet. La couleur sp�culaire est la couleur de la tache sp�culaire que produira la source. Pour un effet r�aliste, elle doit �tre identique � la couleur diffuse.
Ces composantes ne suppriment bien s�r pas les couleurs des mat�riaux : lors du calcul de la couleur des sommets, les couleurs de la lumi�re et du mat�riau sont m�lang�es. Par exemple, une lumi�re cyan (0,1,1) �clairant une sph�re jaune (1,1,0) donnera une sph�re verte (0,1,0). Pour modifier la couleur d'une source de lumi�re, il faut bien s�r appeler glLight(). Par exemple, pour mettre la lumi�re diffuse de la lumi�re n�1 � (.5,.5,1), il suffit de faire
float LightDif[4] = {.5f,.5f,1.f,1.f};
glLightfv(GL_LIGHT0,GL_DIFFUSE,LightDif);
Pour ce qui est de la 4e composante, il s'agit de la composante alpha. Ne vous en pr�occupez pas, elle ne sert � rien pour l'instant (vous pouvez la mettre � 0 si �a vous amuse, �a ne changera rien).

Conclusion

Et ben voil�, je crois qu'on a fait le tour des sources ponctuelles (et directionnelles, qui en sont des d�riv�es). On a tout de m�me vu dans ce tutorial comment allumer une lumi�re, changer sa couleur, sa position, ses facteurs d'att�nuation, et si elle est ponctuelle ou directionnelle. Entrainez-vous bien avec les exemples que je vous ai donn� (surtout au niveau de la position). On continuera la prochaine fois avec les spots.


Vu dans ... http://www-evasion.imag.fr/Membres/Antoine.Bouthors/teaching/opengl/opengl6.html

OpenGL : les spots

Et voici le deuxi�me et dernier chapitre sur les bases des lumi�res. Apr�s avoir bien lu et compris ce tutorial, vous devriez �tre capables de cr�er toutes les lumi�res que vous voulez. Allez, c'est parti :

1. Les normales

Je dois vous avouer un truc : je vous ai cach� quelque chose. Pas une petite chose sans importance, mais vraiment un gros truc, qui m�rite pas mal de pages dans un bon bouquin. Et ce truc, c'est les normales. Si vous �tes accros des bons bouquins sur les modeleurs 3D, vous devez en avoir entendu parler, car il n'y a pas de lumi�re sans normales. Laissez-moi vous expliquer : vous savez d�j� que lorsque un objet illumin�, il est color� par 3 couleurs : ambiente, diffuse et sp�culaire. Pour trouver la lumi�re ambiente, c'est pas dur : on multiplie les composantes ambientes du mat�riau par celle de la lumi�re et paf on applique �a � toutes les faces :
Ambiente(R/G/B) = AmbienteMat�riau(R/G/B) * AmbienteLumi�re(R/G/B)
Par contre, pour la lumi�re diffuse, l'intensit� de cette lumi�re d�pend de chaque face : si la face est perpendiculaire � la direction de la lumi�re, l'intensit� de la lumi�re est maximum. Si par contre la face est en biais, la lumi�re diffuse est plus faible. Et si la face tourne le dos � la source, alors la lumi�re diffuse est nulle. Donc en gros, pour calculer la lumi�re diffuse, on multiplie la composante diffuse de la lumi�re par la composante diffuse du mat�riau, et ensuite on multiplie �a par le sinus de l'angle entre la direction de la lumi�re et la face :
Diffuse(R/G/B) = DiffuseMat�riau(R/G/B) * DiffuseLumi�re(R/G/B) * Sin(alpha)
Si on regarde le dessin, et si on sait ce qu'est un sinus et un cosinus (je vais quand m�me pas redonner les d�finitions), on remarquera ais�ment que Sin(alpha) = Cos(Beta). Je suppose que ce n'est un secret pour personne. Donc maintenant, puisqu'on cherche � faire du temps r�el, faudrait voir � trouver une forumle suffisamment rapide et autre qu'une table de sin pour trouver ce cosinus (ou ce sinus). Et l�, si vous �tes intelligent (ou si avez d�j� lu un bouquin quelconque sur la 3D), vous allez me r�pondre : le produit scalaire ! En effet, pourvu que deux vecteurs soient unitaires, on r�cup�re directement le cosinus entre les deux ! Puisqu'on connait d�j� le vecteur lumi�re, il suffit de trouver l'autre, en pointill�s sur le sh�ma, orthogonal � la face. Et ce vecteur orthogonal, ou vecteur normal (et oui, c'est �a !), comptez pas sur OpenGL pour le trouver tout seul. Et oui, c'est vous qui devez lui dire quelle est la normale de chaque face. Et si je ne vous en ai jamais parl� auparavant, c'est tout simplement parce que GLUT calculait lui-m�me les normales dans glutSphere(), donc on n'avait pas � s'en pr�occuper. Donc la formule de la lumi�re diffuse est :
Diffuse(R/G/B) = DiffuseMat�riau(R/G/B) * DiffuseLumi�re(R/G/B) * (VecteurLumi�re.Normale)
De m�me, la lumi�re sp�culaire s'obtient en v�rifiant si les rayons de lumi�re, r�fl�chis par la face, sont dirig�s vers la cam�ra. Et pour savoir dans quelle direction est r�fl�chie la lumi�re, il faut aussi conna�tre la normale de la face.

2. Per vertex lighting

En fait, OpenGL ne calcule pas les lumi�res face par face, mais sommet par sommet. En effet, si la lumi�re �tait calcul�e pour chaque face, alors toutes les faces auraient une couleur uniforme, ce qui ferait appara�tre les ar�tes entre les faces (ce qu'on appelle le flat shading), et en plus il faudrait calculer le centre de gravit� de la face pour connaitre la direction du vecteur lumi�re. Alors que si la lumi�re est calcul� sommet par sommet, non seulement on connait tout de suite la direction du vecteur lumi�re, mais en plus si deux sommets sont illumin�s diff�remment, on fait un petit d�grad� entre les deux au moment du remplissage de la face, et hop on voit plus les ar�tes. C'est ce qu'on appelle le gouraud shading. Mais on voit aussi l'inconv�nient : moins il y a de sommets, et moins la tache de lumi�re sera pr�cise. Laissez-moi vous expliquer ceci en images :
Les deux images sont les m�mes : il s'agit d'un plan illumin� par un spot rouge. Sauf que sur l'image de gauche, le plan est constitu� par une grille de quelques points alors qu'� droite les mailles de la grille sont beaucoup plus reserr�es. Vous voyez bien que plus il y a de points, et plus l'image est r�aliste. Mais aussi s'il y a plus de points, l'image demandera plus de temps de calcul.
En fait, l'id�al serait de faire du per pixel lighting, c'est-�-dire que la lumi�re serait calcul�e pour chaque pixel de la face, au moment du remplissage de la face (ca donne ce qu'on appelle le phong shading). Evidemment, l� ca serait parfait, quel que soit le nombre de sommets, mais ce serait tellement lent qu'on ne pourrait plus appeler ca du temps r�el. C'est donc une technique qui n'est utilis�e que dans les modeleurs, o� la qualit� pr�vaut sur la vitesse.
En fait je vous dis �a pour une bonne raison : c'est que dans ce tutorial, pour voir les spots, on va s'amuser � �clairer un mur avec des spots et voir ce que ca donne. "Pas compliqu� de dessiner un mur : on fait un petit glBegin(GL_QUADS), 4 glVertex(), un glEnd() et c'est fini", me diriez-vous. Et ben non justement. Parce que si on fait ca, on aura juste 4 sommets aux 4 coins, ca risque de faire un peu trop juste pour voir la tache d'un spot. Donc il faut que notre mur soit en fait une grille plane, de fa�on � ce qu'on distingue bien la lumi�re du spot sur le mur.

3. C'est parti
Et bien allons-y, cr�ons notre mur et notre source de lumi�re, pour commencer :

float MatSpec[4] = {1.0f, 1.0f, 1.0f, 1.0f};
float MatDif[4] = {1.0f, 1.0f, 1.0f, 1.0f};
float MatAmb[4] = {0.3f, 0.3f, 0.3f, 1.0f};
float Light1Pos[4] = {0.0f, 0.0f, 20.0f, 1.0f};
float Light1Dif[4] = {1.0f, 0.2f, 0.2f, 1.0f};
float Light1Spec[4] = {1.0f, 0.2f, 0.2f, 1.0f};
float Light1Amb[4] = {0.5f, 0.5f, 0.5f, 1.0f};
float Spot1Dir[3] = {0.0f, 0.0f, -1.0f};
void InitGL()
{
 | glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,MatSpec); |  |  | //On applique les param�tres du mat�riau
 | glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MatDif);
 | glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT,MatAmb);
 | glLightfv(GL_LIGHT0, GL_DIFFUSE, Light1Dif); |  |  | //Et ceux de la lumi�re
 | glLightfv(GL_LIGHT0, GL_SPECULAR, Light1Spec);
 | glLightfv(GL_LIGHT0, GL_AMBIENT, Light1Amb);
 | glEnable(GL_LIGHTING); |  |  | //Et on allume la lumi�re
 | glEnable(GL_LIGHT0);
} |
void Draw()
{ |
 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 | glMatrixMode(GL_MODELVIEW);
 | glLoadIdentity();
 | gluLookAt(40,40,100,0,0,0,0,1,0);
 | glLightfv(GL_LIGHT0, GL_POSITION, Light1Pos);
 | glNormal3i(0,0,1); |  |  | //D�finit la normale commune � tous les sommets
 | for (int i=-50;i<50;i++)
 | { |
 |  | glBegin(GL_QUAD_STRIP);
 |  | glVertex2i(i,-50);
 |  | glVertex2i(i+1,-50);
 |  | for (int j=-50;j<50;j++)
 |  | { |
 |  |  | glVertex2i(i,j+1); |
 |  |  | glVertex2i(i+1,j+1); |
 |  | } |
 |  | glEnd();
 | } |
 | SwapBuffers(DC) |  |  | // glutSwapBuffers(); pour glut
 | glutPostRedisplay(); |  |  | // Uniquement pour GLUT
} |

Les autres fonctions restent les m�mes. Repompez-les sur les tutorials pr�c�dents.
Ici vous aurez remarqu� qu'on n'a rien appris de nouveau, � part comment sp�cifier les normales pour un sommet : il suffit d'appeler glNormal3{fdbsi}[v]() avant l'appel � glVertex(). Ici, comme la grille est plane, tous les sommets on la m�me normale : (0,0,1).
Le mur est constitu� d'un mat�riau blanc et mat, c'est-�-dire qu'aucune tache sp�culaire n'apparaitra : on ne verra que les couleurs ambiente et diffuse.
Pour ce qui est de la source de lumi�re, elle est telle qu'on en a d�j� vu : omnidirectionnelle, et rouge. Donc si on lance le programme, on verra l'image � gauche:

4. Cutoff
Or ce qu'on veut, nous, c'est un spot. Et ben en fait, notre lumi�re est d�j� � moiti� un spot. En effet; qu'est-ce qui distingue un spot d'une source omni ? L'angle qui d�finit son c�ne de lumi�re, appel� cutoff. Ce cutoff, il est en r�alit� d�j� d�fini, car une omni, c'est tout simplement un spot avec un cutoff de 180� ! Donc tout ce qu'on a � faire pour changer notre omni en spot est de changer ce cutoff, disons � 30� (il peut �tre compris entre 0 et 90, ou 180), gr�ce bien s�r � glLight(). Rajoutez donc dans InitGL()
glLighti(GL_LIGHT0, GL_SPOT_CUTOFF, 30);
Et relancez le programme : vous obtiendrez un truc dans ce genre :

5. Direction
Ca fait un peu bizarre : le spot n'est pas dirig� vraiment perpenciculairement au mur, mais plut�t dans la direction de la cam�ra. C'est parce qu'on doit aussi dire au spot dans quelle direction �clairer. Et comme on ne lui a pas dit, il �claire dans la direction par d�faut : (0,0,-1), et relativement � la cam�ra, car le vecteur direction n'a pas subi les transformations de la matrice modelview. Il faut donc lui dire d'�clairer droit vers le mur, donc selon le vecteur (0,0,-1), mais en prenant compte de la transformation de glutLookAt(). Donc, comme pour sp�cifier la position de la source, il faut sp�cifier la direction du sopt par glLight() apr�s les transformations appliqu�es � la matrice modelview. Rajoutez donc les deux lignes suivantes dans Draw(), apr�s glLight() :
float Light1Dir[3] = {0.0f, 0.0f, -1.0f};
glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, Light1Dir);
Et maintenant le r�sultat est bien ce � quoi on s'attend :

6. Exposant

Mais ce spot n'est pas super convaincant : la tache a une couleur uniforme, et en plus on distigue trop le cr�nelage de la grille. G�n�ralement, un spot �claire plus fort au centre et moins sur le bords. Pour reproduire cet effet, on ne dispose pas comme dans 3DS d'un 'hotspot' et d'un 'falloff', mais on a quand m�me un truc y ressemblant : l'exposant du spot. En effet OpenGL est capable de diminuer exponentiellement l'intensit� de la lumi�re du spot en fonction de la distance par rapport au centre de la tache. Pour accentuer ce d�grad� on peut modifier le facteur expotentiel : plus il est grand et plus la lumi�re sera faible sur les bords. Un petit exemple pour mieux expliquer :

5 10 20 30 128 (max)

Comme vous pouvez le voir, non seulement la tache du spot est beaucoup plus r�aliste, mais en plus on ne distingue plus le cr�nelage de la grille � partir d'un certain niveau. Pour changer cet exposant, il suffit bien s�r d'appeler glLight() avec comme argument GL_SPOT_EXPONENT. Les spots peuvent �galement b�n�ficier de l'att�nutation en fonction de la distance, comme pour toute lumi�re ponctuelle. Notez qu'on ne peut pas faire de spots avec une lumi�re directionnelle, puisque, la source �tant � une distance infinie, on ne peut tenir compte du cutoff.

7. Le LightModel

Notre petit tour sur les lumi�re ne serait pas complet sans avoir parl� du LightModel. Qu'est-ce que c'est (je dois bien d�j� avoir pos� cette question un bon nombre de fois) ? Et bien c'est un ensemble de variables d'�tat qui d�finissent comment est g�r�e l'illumination dans OpenGL, sans se rapporter � une source de lumi�re sp�cifique. Ces variables sont au nombre de 3 : la lumi�re ambiente (GL_LIGHT_MODEL_AMBIENT) de la sc�ne, le mod�le de vue (GL_LIGHT_MODEL_LOCAL_VIEWER) et le mod�le double face (GL_LIGHT_MODEL_TWO_SIDE). Pour changer une de ces variables, on appelle la fonction glLightModel{if}[v](param�tre, valeur), o� param�tre est le nom de la variable � modifier (GL_LIGHT_MODEL_BIDULE), et valeur la valeur � assigner � cette variable.

  • La variable GL_LIGHT_MODEL_AMBIENT est, comme son nom a l'air de l'indiquer, la lumi�re ambiente de la sc�ne enti�re. En fait, lorsque la gestion des lumi�re est activ�e, il y une source qui est toujours pr�sente, et qui �met une lumi�re ambiente sur tout les objets. La valeur par d�faut de cette lumi�re est (0.2, 0.2, 0.2, 1.0) (ne vous pr�occupez pas du 4e param�tre). Pour la modifier, il suffit d'appeler glLightModel{if}v() avec comme nouvelle valeur un tableau de 4 valeurs (int ou float, suivant la fonction).
  • La variable GL_LIGHT_MODEL_LOCAL_VIEWER est le pendant des lumi�res directionnelles/ponctuelles pour la cam�ra. En effet, lorsque la lumi�re sp�culaire d'une face est calcul�e, elle r�sulte d'une op�ration entre le vecteur lumi�re->objet et le vecteur cam�ra->objet. Dans la r�alit�, tous les vecteurs cam�ra->objet rayonnent � partir du centre de la cam�ra, un peu comme une lumi�re ponctuelle rayonne � partir de sa position. On dit alors que le point de vue est local. Ceci oblige OpenGL � recalculer le vecteur cam�ra->objet � chaque fois qu'il a besoin de calculer la composante sp�culaire d'un sommet. Pour acc�l�rer un peu les choses, on peut lui dire que tous les vecteurs cam�ra->objet sont parall�es � l'axe z de la cam�ra, comme pour une source directionnelle, o� tous les veteurs lumi�re sont parall�les. Cela diminue le r�alisme, mais la vitesse de rendu est sensiblement augment�e. Plus le FOV de la cam�ra sera grand, et moins le calcul de la lumi�re semblera r�aliste. Par d�faut, le mod�le local est d�sactiv� (GL_LIGHT_MODEL_LOCAL_VIEWER = 0). Pour l'activer, il suffit d'affecter � la variable une valeur diff�rente de 0.
  • Et enfin, GL_LIGHT_MODEL_TWO_SIDE d�finit si on travaille en illumination simple ou double face. Attention, si vous �tes en simple-face, cela ne veut pas dire qu'OpenGL ne dessinera que la face avant et pas la face arri�re (pour cela il faut utiliser glEnable(GL_CULL_FACE)), mais que le mat�riau attribu� � la face avant sera utilis� pour la face arri�re, et que la normale sera la m�me pour les deux cot�s. Si par contre l'illumination est en double-face, la face arri�re sera dessin�e avec son propre mat�riau, et sa normale sera l'oppos�e de celle de la face avant. Pour d�finir � quel cot� (avant ou arri�re) de la face on affecte tel ou tel mat�riau, il faut sp�cifer GL_FRONT, GL_BACK, ou GL_FRONT_AND_BACK comme premier param�tre de glMaterial() (se reporter aux tutorials pr�c�dents). Si GL_LIGHT_MODEL_TWO_SIDE est � 0 (d�faut), le mod�le simple-face est d�fini. Sinon, on est en mode double-face.

Et bien voil�, on a fait le tour complet des sources de lumi�res. Vous pouvez maintenant faire ce que vous voulez avec les lumi�res pour cr�er votre petite disco personnelle. Pour ceux qui se poseraient des questions, je rapelle quand m�me qu'OpenGL est un moteur temps r�el basique, et que m�me s'il est professionnel, il ne g�re tout de m�me pas les ombres ou autres r�flections. Si vous en voulez, vous serez oblig� de les calculer vous-m�me. Il existe plusieurs m�thodes, plus ou moins rapides et r�alistes, peut-�tre en parlerais-je plus tard. Pour les impatients, allez voir sur le site de sgi, vous aaurez pas mal d'infos l�-dessus (en anglais, bien s�r). En attendant le prochain tutorial, sur les textures, je vous propose de lire la petite annexe ci-dessous :

Annexe : Calculer les normales

Dans l'exemple que je viens de vous montrer, on n'a pas vraiment eu de mal � calculer les normales des points du mur. Mais il se pourrait bien que vous ayez quelques fois des normales � calculer autrement plus complexes, notemment si vous importez des mod�les 3DS ou ASE, o� les normales ne sont pas forc�ment sp�cifi�es. Voici donc quelques techniques couramment utilis�es pour calculer les normales.
Essayons d'abord de calculer une normale unique pour tous les sommets d'un triangle. Si vous avez des notions de maths, vous aurez remarqu� qu'un vecteur normal � un plan peut �tre obtenu � partir de deux vecteurs de ce plan, du moment qu'ils ne sont pas colin�aires. Et bien nous somme g�t�s : dans un triangle, il y a trois [image] c�t�s, donc 3 vecteurs : il suffit d'en prendre 2 et d'en faire le produit vectoriel, et nous avons notre normale (attention � l'ordre du produit vectoriel : il faut que la normale soit orient�e dans le bon sens !). Il faut ensuite la normaliser (c'est-�-dire faire en sorte que sa norme soit �gale � 1) pour que le produit scalaire donne directement le cos(Beta), en divisant chaque composante par la norme du vecteur, car sinon les calculs seront fauss�s. Notez qu'OpenGL peut normaliser les normales pour vous (il suffit pour cela de faire glEnable(GL_NORMALIZE) ), mais ce sera au d�triment de la rapidit�. Donc maintenant que nous avons la normale de notre face, il suffit de l'appliquer aux 3 sommets et c'est fini.
Mais ce n'est pas totalement satisfaisant : cela nous donnera du flat shading : il n'y aura pas de d�grad� entre les sommets, et on verra distinctement les ar�tes, comme dans la pi�tre image que vous voyez au dessus. Pour qu'on ne voie pas les ar�tes, ce qu'il faudrait, c'est que deux sommets ayant les m�mes coordonn�es aient la m�me normale, quelle que soit la face � laquelle ils appartiennent. Donc il faut parcourir les sommets et lorsqu'on en trouve plusieurs au m�me endroit, on fait la moyenne des normales de ces sommets et on leur attribue � tous cette normale moyenne. Et l� ch'est machik : cha marche !

Voil� le tutorial est termin�. Si vous avez des questions, vous savez � qui vous adresser. Dans les sources � t�l�charger se trouve un petit programme qui montre 3 spots se courant apr�s.


Vu dans ... http://www-evasion.imag.fr/Membres/Antoine.Bouthors/teaching/opengl/opengl7.html

OpenGL : les textures

Introduction

Autant vous le dire tout de suite : le texturing est un sujet tellement vaste qu'il ne suffirait pas d'un seul tutorial pour en faire le tour, ni m�me 3 ou 4. Les possibilit�s qu'offrent OpenGL sont tellement vastes dans ce domaine que les applications de cet outils sont quasi infinies. Donc l�, on va juste voir les bases (ce qui vous permettra quand m�me de faire des trucs bien sympa), c'est-�-dire comment d�clarer une texture � OpenGL, l'appliquer sur un polygone gr�ce aux TexCoord, et aussi quelques outils qui vont avec tel que le filtering, la r�p�tition... Ca en fait d�j� pas mal je pense, et pour ce qui est du mipmapping, de la transparence ou des textures d'environnement, on verra �a plus tard, OK ?

1. C'est parti

Commencons par le commencement, c'est-�-dire par activer la gestion des textures. Si vous avez bien tout pig�, vous aurez devin� qu'il suffit de faire un petit glEnable() avec le param�tre appropri�. Vous avez gagn� ! Rajoutez donc dans notre petite InitGL() la ligne suivante :

glEnable(GL_TEXTURE_2D);

Ca active la gestion des textures 2D (ben ouais, ca existe les textures 1D ou 3D). Nous devons maintenant cr�er un nouvel objet texture. Pour nous, ce sera juste un num�ro, mais c'est ce num�ro qui nous permettra de sp�cifier � OpenGL toutes les param�tre de la texture, la texture elle-m�me, et aussi de lui dire quelle texture on veut mapper. Pour cela il faut utiliser glGenTextures(), qui nous renvoie autant de num�ros que de textures demand�es. Ici on veut qu'une seule texture, donc on va r�cup�rer un num�ro, � bien retenir.
Ensuite, nous d�cidons quelle est la texture courante gr�ce � glBindTexture(). La texture courante, c'est celle sur laquelle OpenGL va effectuer les modifs lorsqu'on lui demandera, et c'est aussi la texture qiu sera mapp�e sur les objets. Donc si vous avez des objets avec des textures diff�rentes, il suffira d'appeler glBindTexture() avec le bon num�ro de texture juste avant de dessiner l'objet sur lequel devra �tre mapp� la texture.
Et enfin, nous allons transmettre � OpenGL toutes les caract�ristiques de la texture : largeur, hauteur, format, etc... et bien s�r l'image elle-m�me, gr�ce � glTexImage2D(). Pour l'instant, pour simplifier les choses, j'ai choisi d'appliquer � notre objet une texture de 2 pixels par 2 pixels, en forme d'�chiquier (et ouais, je me suis pas foul�). Voil� donc tout ce que nous devons �crire :

GLubyte Texture[16] =
{
0,0,0,0, 0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF, 0,0,0,0
};

 //Image (2x2)
GLuint Nom;
void InitGL()
{
glClearColor(.5,.5,.5,0); | //Change la couleur du fond
glEnable(GL_DEPTH_TEST); | //Active le depth test
glEnable(GL_TEXTURE_2D); | //Active le texturing
glGenTextures(1,&Nom); | //G�n�re un n� de texture
glBindTexture(GL_TEXTURE_2D,Nom); | //S�lectionne ce n�
glTexImage2D (
GL_TEXTURE_2D, | //Type : texture 2D
0, | //Mipmap : aucun
4, | //Couleurs : 4
2, | //Largeur : 2
2, | //Hauteur : 2
0, | //Largeur du bord : 0
GL_RGBA, | //Format : RGBA
GL_UNSIGNED_BYTE, | //Type des couleurs
Texture | //Addresse de l'image
);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}

Voil�, j'ai fait ce que j'avais annonc� : J'appelle glGenTextures() avec le nombre de textures voulues et le tableau dans lequel il doit les renvoyer (ici il n'y en a qu'une donc autant mettre &Nom). J'appelle ensuite glBindTexture() pour lui dire avec quelle texture je vais travailler. Et enfin, glTexImage2D(), pour lui balancer toutes les infos dont il a besoin. Les commentaire vous disent d�j� � quoi correspondent les param�tres, mais je vais quand m�me insister sur 2 ou 3 choses :

  • Pour ce qui est du mipmap, j'ai dit qu'on verra ca plus tard, donc je n'en parlerais pas. Contentez-vous de mettre ce param�tre � 0
  • Le 3e param�tre c'est le nombre de composantes de couleurs par pixel. Ici il y en a 4 : R,G, B et A (pour Rouge, Vert, Bleu et Alpha, pour ceux qu'auraient pas encore tout pig�). On peux en avoir de 1 � 4.
  • Ensuite viennent la largeur et la hauteur. Elles ne sont pas forc�ment �gales, mais doivent �tre imp�rativement de la forme 2^n. Si une de ces valeurs n'est pas une puissance de 2, ca ne marchera pas. Si vous �tes oblig� de bosser sur des textures batardes, utilisez gluScaleImage() pour les mettre � l'�chelle (voyez avec votre compilateur ou le Reference Manual pour en savoir plus sur cette fonction).
  • Le param�tre suivant est la largeur du bord de la texture. En effet, on peut rajouter une bordure d'un pixel autour de l'image, et d'une couleur unique. Cela peut servir pour �viter d'avoir des r�sultat bizarre sur les bords avec le filtering, mais on ne l'utilise pas souvent.
  • Vient ensuite le format de stockage. Ici, j'ai rang� les octets de ma texture dans le format classique RGBA, donc je dis � OpenGL de les lire suivant le format RGBA. On peut mettre GL_COLOR_INDEX, GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_RGB, GL_RGBA, GL_BGR_EXT, GL_BGRA_EXT, GL_LUMINANCE, et GL_LUMINANCE_ALPHA. Amusez-vous � changer ce param�tre pour voir ce que ca donne.
  • Le 8e param�tre d�finit sous quel type sont enregistr�es les composantes. Ici, ma texture est un tableau de GLubyte (c'est-�-dire unsigned char), donc je dit � OpenGL de les lire en tant que GLubyte. Ce param�tre peut �tre GL_UNSIGNED_BYTE, GL_BYTE, GL_BITMAP, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT, GL_INT, ou GL_FLOAT.
  • Et enfin, on donne � OpenGL l'endroit o� est stock�e l'image, histoire qu'il puisse la mapper.

Remarque : Vous vous �tes peut-�tre demand� pourquoi j'utilise une texture RGBA et pas RGB, alors qu'on a m�me pas envore vu ce qu'�tait la composante A. En fait, je fais �a pour deux raisons : d'abord, comme on bosse sur des processeurs 32 bits, la gestion de la m�moire est optimis�e pour les types 32 bits. De plus, si vous essayez de mettre une texture RGB � la place, il y de fortes chances pour que le r�sultat ne soit pas celui que vous attendez. En effet, par d�faut, OpenGL consid�re que chaque pixel occupe 4 octets. Donc il va lire les pixels de l'image 4 par 4, ce qui n'est pas vraiment ce qu'on veut. Pour r�soudre ce probl�me, il faut appeler glPixelStorei(GL_UNPACK_ALIGNMENT,1), mais je n'en dirais pas plus, car si on veut quelque chose d'optimis� et de compatible, on utilise des textures RGBA.

Je vous entends d�j� dire "Et les deux derni�res lignes ?". Pour l'instant je n'en parle pas, contentez-vous de les recopier b�tement, on verra �a plus tard.

2. Coordonn�es de textures (TexCoord)
Et ben �a y est, mine de rien, notre texture est charg�e et pr�te � �tre apliqu�e : on n'a plus qu'� dessiner un truc sur lequel l'appliquer. Pourquoi pas un joli p'tit cube ? Et ben allons-y alors !

void Draw()
{
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); | //Efface le framebuffer et le depthbuffer
glMatrixMode(GL_MODELVIEW); | //Un petit gluLookAt()...
glLoadIdentity();
gluLookAt(3,2,3,0,0,0,0,1,0);
glBegin(GL_QUADS); | //Et c'est parti pour le cube !

glTexCoord2i(0,0);glVertex3i(-1,-1,-1);
glTexCoord2i(1,0);glVertex3i(+1,-1,-1);
glTexCoord2i(1,1);glVertex3i(+1,+1,-1);
glTexCoord2i(0,1);glVertex3i(-1,+1,-1);

 //1 face

glTexCoord2i(0,0);glVertex3i(-1,-1,+1);
glTexCoord2i(1,0);glVertex3i(+1,-1,+1);
glTexCoord2i(1,1);glVertex3i(+1,+1,+1);
glTexCoord2i(0,1);glVertex3i(-1,+1,+1);

//2 faces

glTexCoord2i(0,0);glVertex3i(+1,-1,-1);
glTexCoord2i(1,0);glVertex3i(+1,-1,+1);
glTexCoord2i(1,1);glVertex3i(+1,+1,+1);
glTexCoord2i(0,1);glVertex3i(+1,+1,-1);

 //3 faces

glTexCoord2i(0,0);glVertex3i(-1,-1,-1);
glTexCoord2i(1,0);glVertex3i(-1,-1,+1);
glTexCoord2i(1,1);glVertex3i(-1,+1,+1);
glTexCoord2i(0,1);glVertex3i(-1,+1,-1);

//4 faces

glTexCoord2i(1,0);glVertex3i(-1,+1,-1);
glTexCoord2i(1,1);glVertex3i(+1,+1,-1);
glTexCoord2i(0,1);glVertex3i(+1,+1,+1);
glTexCoord2i(0,0);glVertex3i(-1,+1,+1);

 //5 faces

glTexCoord2i(1,0);glVertex3i(-1,-1,+1);
glTexCoord2i(1,1);glVertex3i(+1,-1,+1);
glTexCoord2i(0,1);glVertex3i(+1,-1,-1);
glTexCoord2i(0,0);glVertex3i(-1,-1,-1);

 //6 faces
glEnd();
SwapBuffers(DC); | //glutSwapBuffers(); pour GLUT
glutPostRedisplay(); | //Uniquement pour GLUT
}

Et voil�. Si vous lancez le programme maintenant, ca va donner une magnifique image de cube mapp� avec une texture d'�chiquier, comme �a :

Euh, bah non en fait elle est pas si magnifique que ca l'image : y s'passe des trucs bizarres. En fait, c'est parce que OpenGL y va un peu trop � la barbare pour appliquer les textures. En effet, si on r�fl�chit bien, on se rend compte que pour que l'effet de mapping soit r�aliste, il faut tenir compte de la profondeur. Prenons l'exemple d'un triangle dont un des sommets est tr�s �loign� de l'utilisateur par rapport aux deux autres, comme �a :
L� la texture est correctement appliqu�e, et on a bien l'impression que le triangle est align� sur l'axe Z.

Et bien si on ne tient pas compte de la profondeur, la texture est appliqu�e comme si le triangle �tait face � nous, comme OpenGL le fait actuellement pour notre cube, et ca donnerait �a :
Et l�, c'est carr�ment pas ce qu'on veut. Donc il faut dire � OpenGL d'effectuer une correction de perspective sur les textures (et par la m�me occasion sur les couleurs). Pour cela, rajoutez dans InitGL() la ligne suivante :

glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);

Et maintenant, voil� la magnifique image qu'on obtient. C'est quand m�me un petit peu mieux, non ?
Bon maintenant voyons un peu ce que notre fonction a dans la bide : ici on d�couvre une nouvelle fonction : glTexCoord{1234}{sifd}[v](). C'est elle qui nous permet de dire � OpenGL quel morceau de la texture est attach� � quel sommet. Ici on a une texture 2 dimensions, donc on utilise glTexCoord2*(). On lui passe en param�tre 2 valeurs, qui sont les coordonn�es du point de la texture � attacher au sommet qui va suivre. Le point (0,0) correspond au coin sup�rieur gauche de la texture, et (1,1) au coin inf�rieur droit. Mais on peut aussi choisir n'importe quel point de la texture (ou texel) entre 0 et 1. Par exemple si on avait voulu mettre que du blanc, il aurait suffit de mapper la partie sup�rieure gauche de la texture, donc de remplacer les 1 par des 0.5 (et aussi d'utiliser glTexCoord2f(), parce que sinon les TexCoord auraient �t� transform�es en int).

3. Filtering
Imaginez que vous �tes dans un simulateur de vol dernier cri : si vous regardez le sol, et m�me si vous �tes tr�s pr�s, normalement vous ne disinguez pas les pixels qui composent la texture. Alors que si vous jouez � un vieux je du genre de Duke Nukem ou Quake, si vous vous collez � un mur vous verrez distinctement les pixels de la texture, comme dans notre exemple. Mais dans Quake 3, ou dans un simulateur de vol, vous ne verrez jamais la s�paration nette entre les pixels : il y aura toujours un d�grad� entre le pixel et son voisin. Cette petite r�volution du monde de la 3D s'appelle le bilinear filtering, et est parfaitement g�r�e par OpenGL. Vous vous rappelez les deux lignes que je n'ai pas expliqu� tout-�-l'heure ? Et bien elles disaient � OpenGL de ne pas faire de bilinear filtering, mais de prendre le texel le plus proche (d'o� le terme GL_NEAREST) lorsqu'il dessine le pixel d'un polygone. Changez donc ce param�tre en GL_LINEAR dans les deux lignes, et voyez le r�sultat � droite.
On voit bien qu'il y a un d�grad� entre chaque texel, d'ailleurs on le voit un peu trop bien, puisque l� on a une texture de 4 pixels (2x2), �a fait un peu juste, mais bon. Voyons plut�t cette fonction glTexParameter{if}[v]() : avec elle on r�gle le filtering, mais en 2 fois. Pourquoi ? Et bien en fait il y a 2 filtering : un lorsqu'il y a magnification (GL_MAG), c'est-�-dire lorsque un texel s'�tend sur plusieurs pixels, comme ici, en un lorsqu'il y a minification (GL_MIN), c'est-�-dire lorsqu'un pixel contient plusieurs texels. Dans le cas de la magnification, on l'a vu, le pixel est le r�sultat de la moyenne des texels environnants. Dans le cas de la minification, s'il y a filtering, le pixel est le r�sultat de la moyenne des texels contenus.

4. R�p�tition
Mais il y a quand m�me un truc bizarre : sur les bords des faces, la texture subi un d�grad� vers le gris, alors que normalement elle devrait subi un d�grad� vers la coumeur du pixel de la texture (blanc ou noir). Pourquoi ? Et bien parce qu'OpenGL est configur� pour r�p�ter la texture. C'est-�-dire que si on avait mis une valeur n>1 pour les texcoords, il aurait r�p�t� n fois la texture (essayez pour voir). Et lorsqu'il est au bord, il fait un filtering avec le bord oppos� de la texture, ce qui conduit � rendre tous les bords... gris !
En mode GL_CLAMP ("brid�"), les coordonn�es sont ramen�es entre 0 et 1 et sur les c�t�s de la texture, le filtering est fait entre la texture et le bord (celui que l'on sp�cifie dans glTexImage2D() ). Pour choisir le mode de texuring , il faut appeler glTexParameter() avec comme premier param�tre GL_TEXTURE_2D, comme second param�tre GL_TEXTURE_WRAP_S (pour affecter les coordonn�es horizontales) ou GL_TEXTURE_WRAP_T (pour les coord. verticales), et comme troisi�me param�tre GL_CLAMP (pour brid�) ou GL_REPEAT (pour r�p�t�). Et si on est en mode r�p�t�, on obtient bien le r�sultat que voici. Ici, comme la couleur du bord est (0,0,0,0) par d�faut, il y a un d�grad� vers le noir sur les bords des faces.
Vous �tes en train de vous demander comment ne pas faire de filtering sur les bord ? Et ben vous pourrez chercher longtemps, parce que... on peut pas. H� non ! Donc le meilleur moyen est encore de se mettre en GL_REPEAT de de faire des textures dont les bords oppos�s sont semblables, ou d'utiliser le bord en mode GL_CLAMP si les couleurs ne varient pas trop.
[Rectification : Maintenant, avec l'extension ARB_texture_border_clamp, il est possible de ne pas faire de filtering sur les bords. Il suffit d'utiliser la constante CLAMP_TO_BORDER_ARB (ou CLAMP_TO_BORDER_ARB, suivant votre version d'OpenGL) � la place de GL_REPEAT ou GL_CLAMP.]
Ah oui j'oubliais : pour changer la couleur du bord, on appelle glTexParameter{if}v() avec GL_TEXTURE_BORDER_COLOR comme 2e param�tre et un tableau de 4 valeurs (RGBA pour changer) comme 3e param�tre.

Conclusion

On en a d�j� vu pas mal dans ce tutorial, et pourtant il reste encore beaucoup � dire sur le sujet : il y a le mipmapping, le blending... Et saviez-vous par exemple que les textures poss�dent aussi leur propre matrice ? Ben ouais, apr�s tout, appliquer une texture, c'est une simple affaire de projection, non ? (ouh l�, je commence � me Weberiser, moi - private joke). Donc tout �a, on le verra plus tard, parce que moi, l�, j'ai faim. Alors amusez-vous bien avec les sources que vous pouvez t�l�charger en bas, et avec l'annexe que voici.

Annexe : charger des images BMP

Bon OK je dois bien l'avouer, y'a pas que des �chiquers de 4 pixels dans le monde de la 3D. G�n�ralement, y'a plut�t des textures assez complexe qu'on aurait du mal � la main, mais qui, bizarrement, sont plus facilement �ditables sous formes d'images (.bmp par exemple...). H� h�, je vous vois venir : "cool, il va nous filer un [image] code tout pr�t pour loader des bmp !!". Et ben nan, na ! D'abord je me suis fais chier � d�crire le format BMP sur ce site (allez, cherchez un peu), alors c'est pas pour des prunes, vous pouvez faire un loader vous-m�mes, ca devrait durer 15 min � tout casser. Mais bon pour les quiches qui auront le courage de pomper le tutorial et les sources, je vais quand m�me inclure une petite fonction de chargement de BMP, histoire de pas �tre trop salaud (vous pouvez voir le r�sultat � droite).
Et si vous n'avez vraiment pas envie de vous cassez le cul, il existe dans la librairie glaux.h une fonction toute faite pour ce genre de choses. Allez sur le site de Nehe pour voir comment l'utiliser.
Sinon, pour ceux qui auraient l'intention de charger leurs images tous seuls, quelques remarques :

  • D'abord, n'oubliez pas que l'image est invers�e. Cela veut non seulement dire que la ligne du bas est stock�e en premier, mais aussi que le format est donc BGR, et non RGB. Donc logiquement il suffirait d'appeler glTexImage2d() avec comme format GL_BGR_EXT et paf ca marche. En effet, th�oriquement, �a marche. Mais pas partout. En effet, sur certaines cartes 3D (des ATI All-InWonder, pour ne pas le nommer), la texture �tait quand m�me charg�e en RGB. Donc pr�f�rez inerser les composantes R et B de chaque pixel avant d'envoyer la texture � OpenGL, ca vous �vitera de faire des patchs. Vous pouvez aussi par la m�me occasion la mettre sens dessus-dessous (je veux dire faire un 'flip' pour la remettre dans le bon sens).
  • Ensuite, gare au champ 'Taille de l'image' � l'offset 0x22 dans les fichiers BMP : il n'est pas toujours rempli par tous les logiciels, car on peu le retrouver gr�ce � la largeur et la hauteur. Donc �viter de l'utiliser.


Vu dans ... http://www-evasion.imag.fr/Membres/Antoine.Bouthors/teaching/opengl/opengl8win.html

OpenGL : l'input avec Windows

Introduction

Jusqu'� maintenant, nous avons fait pas mal de trucs, tous toujours plus beaux les uns que les autres, mais nous n'avons jamais pu vraiment contr�ler les objets. Ce que nous faisions de mieux c'�tait tourner autour plus ou moins vite et puis c'est tout. En plus la vitesse de rotation d�pendait de la vitesse d'affichage, et vu que c'�tait pr�vu pour tourner de fa�on fluide en software, on distinguait plus grand chose avec une GeForce... Nous allons donc apprendre comment g�rer les interactions utilisateurs : souris, clavier, et pourquoi pas tablette graphique. Il n'y aura pas de code OpenGL sp�cifique dans ce tutorial, mais vous pourrez l'utiliser pour modifier les exemples des tuts pr�c�dents.

1. G�n�ralit�s

Bon, vous aurez compris que les interactions se g�rent comme le reste : gr�ce aux messages envoy�s � la WinProc. Donc tout ce que nous allons faire c'est lister les messages qui nous int�ressent.

2. Le clavier

Le principal message concernant le clavier est WM_CHAR. Le caract�re est contenu dans wParam (� convertir en TCHAR). Ce message n'est pas g�n�r� directement, mais c'est une traduction du message WM_KEYDOWN. Ce dernier g�re tous les messages provenant du clavier, m�me les touches �tendues. Si la touche est une touche ASCII normale, alors TranslateMessage() g�n�rera un message WM_CHAR � partir du WM_KEYDOWN, pour que le message soit plus simple � g�rer. Si vous n'appelez pas TranslateMessage(Msg) pour chaque message, vous devrez g�rer tous les messages provenant du clavier � travers le message WM_KEYDOWN. Pour ce message, wParam contient le code de la touche virtuelle enfonc�e. Voici la liste des touches virtuelles possibles :

VK_LBUTTON (bouton gauche de la souris)
VK_RBUTTON (bouton droit de la souris)
VK_MBUTTON (bouton milieu de la souris)
VK_CANCEL
VK_BACK
VK_TAB
VK_CLEAR
VK_RETURN
VK_SHIFT
VK_CONTROL
VK_MENU (Alt)
VK_PAUSE
VK_CAPITAL (Caps Lock)
VK_ESCAPE
VK_SPACE
VK_PRIOR (Page Up)
VK_NEXT (Page Down)
VK_END
VK_HOME
VK_LEFT
VK_UP
VK_RIGHT
VK_DOWN
VK_SELECT (???)
VK_EXECUTE (???)
VK_SNAPSHOT (Print Screen)
VK_INSERT
VK_DELETE
VK_HELP
VK_0, ... , VK_9
VK_A , ... , VK_Z
VK_LWIN (touche 'Windows' gauche - pour les claviers windows)
VK_RWIN (touche 'Windows' droite - pour les claviers windows)
VK_APPS (touche 'Apps' - pour les claviers windows)
VK_NUMPAD0, ... , VK_NUMPAD9
VK_MULTIPLY (*)
VK_ADD (+)
VK_SUBTRACT (-)
VK_DECIMAL (.)
VK_DIVIDE (/)
VK_F1, ... , VK_F24
VK_NUMLOCK
VK_SCROLL (Scroll Lock)
VK_ATTN (???)
VK_CRSEL (???)
VK_EXSEL (???)
VK_EREOF (???)
VK_PLAY (???)
VK_ZOOM (???)
VK_NONAME (???)
VK_PA1 (???)
VK_OEM_CLEAR (???)

Les touches sans parenth�ses se passent de commentaires, celles comment�es par (???) ne sont pas sp�cifiques aux claviers standards PC.

Vous aurez aussi remarqu� que les clics de souris peuvent aussi �tre g�r�s gr�ce aux touches virtuelles : le message WM_KEYDOWN n'est donc pas un simple message clavier, mais g�re aussi pas mal de touches sp�ciales.

3. La souris

L� par contre y'a pas mal de messages. Voici la liste des messages utiles :

  • WM_LBUTTONDBLCLK, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDBLCLK, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_RBUTTONDBLCLK, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_MOUSEMOVE : Ces messages se comprennent d'eux-m�mes. Les valeurs de wParam et lParam sont les m�mes pour tous : wParam contient les autres touches appuy�s en m�me temps : MK_CONTROL, MK_SHIFT ... et MK_LBUTTON, MK_MBUTTON, MK_RBUTTON. LOWORD(lParam) et HIWORD(lParam) renvoient quand � eux les coordonn�es x et y du curseur.
  • WM_MOUSEWHEEL : C'est � peu pr�s la m�me chose que pour WM_MOUSEMOVE, sauf que l� les touches parall�les sont stock�es dans LOWORD(wParam), et HIWORD(wParam) contient la valeur de la rotation de la molette.

Il y en a quelques autres, mais ils ne sont pas suffisamment utiles pour �tre pr�sent�s ici, et de toute fa�on vous avez la liste de tous les messages dans l'aide de VC++.

Ben voil� c'est d�j� termin�. Pas de source � t�l�charger aujourd'hui, puisqu'on a rien vu de particulier niveau OpenGL.

Antoche


Vu dans ... http://www-evasion.imag.fr/Membres/Antoine.Bouthors/teaching/opengl/opengl8glut.html

OpenGL : l'input avec GLUT

Introduction

Jusqu'� maintenant, nous avons fait pas mal de trucs, tous toujours plus beaux les uns que les autres, mais nous n'avons jamais pu vraiment contr�ler les objets. Ce que nous faisions de mieux c'�tait tourner autour plus ou moins vite et puis c'est tout. En plus la vitesse de rotation d�pendait de la vitesse d'affichage, et vu que c'�tait pr�vu pour tourner de fa�on fluide en software, on distinguait plus grand chose avec une GeForce... Nous allons donc apprendre comment g�rer les interactions utilisateurs : souris, clavier, et pourquoi pas tablette graphique. Il n'y aura pas de code OpenGL sp�cifique dans ce tutorial, mais vous pourrez l'utiliser pour modifier les exemples des tuts pr�c�dents.

1. G�n�ralit�s

Bon, vous avez vraiment du bol que je fasse �a, parce que sous GLUT, c'est tellement simple que c'est presque inutile de faire un tutorial l�-dessus. Mais bon puisque y'en a un sous Windows, autant faire le pendant pour GLUT.
Donc, si vous connaissez l'anglais et que vous avez pas envie de vous taper toute cette page, allez directement � la page de specs de GLUT, et vous trouverez tout de suite ce qu'il vous faut. En tout cas le principe n'est pas dur � comprendre : jusqu'ici, on utilisait par exemple glutDisplayFunc() pour dire � GLUT quelle fonction se chargeait de l'affichage, et ben c'est toujours pareil : on va dire � GLUT quelle fonction g�rera le clavier ou la souris, c'est pas plus dur que �a !

2. Le clavier

Les touches re�ues par GLUT sont divis�es en 2 parties : les touches simples, qui peuvent �tre traduites par un char, et les touches sp�ciales, qui correspondent � un code ASCII �tendu (fl�ches, touches de fonction...). La gestion du clavier est donc s�par�e en 2 fonctions : celle qui g�re les touches normales, et celle qui g�re les touches sp�ciales :

1.1 glutKeyboardFunc()

Cette fonction sert � sp�cifier � GLUT quelle fonction s'occupe de la gestion des touches normales. Elle prend donc en param�tre le nom de la fonction. La fonction de gestion doit avoir 3 param�tres : le code ASCII de la touche, et la position x et y de la souris. Un petit exemple :

void GestionClavier(unsigned char key, int x, int y)
{ |
 | printf("vous avez appuy� sur %c ",key);
 | printf("position de la souris : ");
 | printf("%d,%d ",x,y);
 | if (key==27) exit(0);
} |
int main()
{ |
 | // Initialisation de GLUT etc...
 | glutKeyboardFunc(GestionClavier);
 | glutMainLoop();
} |

C'est aussi simple que �a : votre fonction peut s'appeler n'importe comment, mais elle doit forc�ment recevoir les param�tres (unsigned char, int, int), sinon GLUT n'est pas content. Ici la fonction ne fait qu'afficher les param�tres, et quitte le programme si l'utilisateur a appuy� sur Escape (dont le code ASCII est 27 comme chacun sait).

1.2 glutSpecialFunc()

Bon, je suppose que vous aurez compris � quoi sert cette fonction. La diff�rence ici, c'est que la touche n'est plus un caract�re ASCII, mais un int. Un petit exemple :

void GestionSpecial(int key, int x, int y)
{

printf("vous avez appuy� sur ")
switch (key)
{
case GLUT_KEY_F1 :
case GLUT_KEY_F2 :
case GLUT_KEY_F3 :
case GLUT_KEY_F4 :
case GLUT_KEY_F5 :
case GLUT_KEY_F6 :
case GLUT_KEY_F7 :
case GLUT_KEY_F8 :
case GLUT_KEY_F9 :
case GLUT_KEY_F10 :
case GLUT_KEY_F11 :
case GLUT_KEY_F12 :
case GLUT_KEY_LEFT :
case GLUT_KEY_UP :
case GLUT_KEY_RIGHT :
case GLUT_KEY_DOWN :
case GLUT_KEY_PAGE_UP :
case GLUT_KEY_PAGE_DOWN :
case GLUT_KEY_HOME :
case GLUT_KEY_END :
case GLUT_KEY_INSERT :

printf("F1 ");break;
printf("F2 ");break;
printf("F3 ");break;
printf("F4 ");break;
printf("F5 ");break;
printf("F6 ");break;
printf("F7 ");break;
printf("F8 ");break;
printf("F9 ");break;
printf("F10 ");break;
printf("F11 ");break;
printf("F12 ");break;
printf("Gauche ");break;
printf("Haut ");break;
printf("Droite ");break;
printf("Bas ");break;
printf("Page up ");break;
printf("Page down ");break;
printf("Home ");break;
printf("End ");break;
printf("Insert ");break;

}
printf("position de la souris : ");
printf("%d,%d ",x,y);
}
int main()
{
// Initialisation de GLUT etc...
glutSpecialFunc(GestionSpecial);
glutMainLoop();
}

Et voil�, gr�ce � cet exemple vous avez tout pig�, et en plus vous avez vu toutes les valeurs que pouvait prendre la touche.
Voil� pour ce qui est du clavier. La fonction glutGetModifiers() est aussi en relation avec le clavier, mais comme elle s'applique aussi � la souris, je n'en parlerais qu'� la fin.

3. La souris

Cette fois-ci, GLUT nous fournit un peu plus de fontions. En voici la liste :

  • glutMouseFunc() : d�finit la fonction � appeler lorsqu'un bouton de la souris est appuy� ou rel�ch�. La fonction utilisateur doit avoir les param�tres (int button, int state, int x, int y), o� button peu valoir GLUT_LEFT_BUTTON, GLUT_MIDDLE_BUTTON, ou GLUT_RIGHT_BUTTON, state vaut GLUT_UP ou GLUT_DOWN, et x et y sont les coordonn�es de la souris par rapport � le fen�tre.
  • glutPassiveMotionFunc() : d�finit la foncion � appeler lorsque la souris bouge. Les param�tres de la fonction utilisateur doivent �tre du type (int x, int y), o� x et y sont les coordonn�es du pointeur de la souris.
  • glutMotionFunc() : m�me chose que la pr�c�dente, sauf que la fonction utilisateur ne sera appel�e que si la souris bouge et qu'un bouton est appuy� (comme dans un drag'n drop, par exemple)
  • glutEntryFunc() : c'est pas tr�s utile, mais bon. Cette fonction sert � d�finir la fonction � appeler lorsque la souris sort ou rentre dans la fen�tre. La fonction utilisateur ne recoit qu'un param�tre, de type int, qui vaut GLUT_LEFT si la souris sort de la fen�tre, ou GLUT_ENTERED si la souris rentre dans la fen�tre.

4. Les autres

GLUT sait �galement g�rer les tablettes graphiques et autres spaceballs, mais comme tout le monde ne s'en sert pas, je ne vais pas encombrer ce tutorial avec �a. Pour ceux que ca int�resse, ils n'auront qu'� aller voir eux-m�me dans les pages de r�f�rences de GLUT.
Par contre il reste une fonction qui sert � la fois pour la souris et pour le clavier : la fonction glutGetModifiers(). Ce n'est pas une fonction de callback comme les pr�c�dentes, mais une fonction d'�tat : elle sert � savoir si les touches Ctrl, Alt ou Shift sont appuy�es. Il ne faut l'appeler que dans un fonction de gestion du clavier ou de la souris. Elle ne prend aucun param�tre et renvoie un int qui peut valoir GLUT_ACTIVE_SHIFT, GLUT_ACTIVE_CTRL ou GLUT_ACTIVE_ALT.

Ben voil� c'est d�j� termin�. Pas de source � t�l�charger aujourd'hui, puisqu'on a rien vu de particulier niveau OpenGL.


OpenGL WWW Pages Intro

The OpenGL WWW Pages

**Note** This collection of web pages is a local Tulane copy of materials provided by Digital Equipment Corporation for public distribution.

The OpenGL man pages have been reformatted into HTML for your reference. At this time it is not part of our Open3D product, but merely a convenience for our customers. (And web surfers.)

HTML does not have a good way to write mathematic equations. I have done my best to represent the various formulas in the man pages into something readable on character-based readers. I have used one ISO Latin-1 one character, the raised dot (·, hex b7), to indicate multiplication. If this is a problem for people I can change it to `*'. Superscripts are placed on the line above and subscripts are placed on the line below their referent. Fortunately I didn't run across any instances of subscripts on one line followed by superscripts on the line below.

My primary goals are

  1. Make the man pages readable and correct.
  2. Make sure the formulas, equations and tables are readable and correct.
  3. Keep all information visible for character based readers.
Making everything look pretty is not as important.

If you find any errors or if you have any suggestions, please send mail to andy.vesper@eng.pko.dec.com or andy.vesper@pko.mts.dec.com. I will try to fix them up as I find time. Unfortunately this is a volunteer effort and I so I cannot spend much time on it.

To start, choose either the alphabetic order page or specification order page.

There is also an introductory page located at glXIntro.

The GLU tesselator has changed in version 1.2; here are some notes on the new GLU tesselator

Due to popular demand, I have added a tar file and a zip file, each of which contains all the .html sources. Note that the names are long names with the extension .html, and so are not appropriate to being placed on a DOS disk.

For more OpenGL information, I suggest starting at The OpenGL WWW Center.

You can also read the hypertext version of The OpenGL Graphics System: A Specification (Version 1.0). In this document all formulae are given as images, so they look much better than my ASCII art.

Last Edited: Tuesday, October 24, 1995

AFV


GLU Version 1.2 Polygon Tessellator Notes

Some notes on the new polygon tessellator in GLU version 1.2

GLU version 1.2 introduces a new polygon tessellator. The following routines have become obsolete and should not be used for new development based on GLU version 1.2 and later.
    Obsolete Routine	Replace with

    gluBeginPolygon		gluTessBeginPolygon; gluTessBeginContour
    gluEndPolygon		gluTessEndContour; gluTessEndPolygon
    gluNextContour		gluTessEndContour; gluTessBeginContour

The following routines are new in GLU version 1.2:

    gluGetTessProperty
    gluTessBeginContour
    gluTessBeginPolygon
    gluTessEndContour
    gluTessEndPolygon
    gluTessNormal
    gluTessProperty

Some routines have had an argument or return-value changed from ``GLUtriangulatorObj *'' to ``GLUtesselator *''. Since the GLU header file for version 1.2 has a typedef from GLUtriangulatorObj to GLUtesselator, source code should not need any immediate changes. However, at some time the source code should be moved to the GLU version 1.2 name.

    gluBeginPolygon
    gluDeleteTess
    gluEndPolygon
    gluNewTess
    gluNextContour
    gluTessCallback
    gluTessVertex

One routine has been expanded to support more valid values for the which argument:

    gluTessCallback

To distinguish what version of the GLU library you are using at compile time, check for the following pre-processor symbols:

    none defined			version 1.0
    GLU_VERSION_1_1			version 1.1 or later
    GLU_VERSION_1_2			version 1.2 or later

Digital Open3D supports the following versions of the GLU library:

    Digital Open3D Version		GLU Version Supported

    Before Version 2.6		version 1.0
    Version 2.6			version 1.1
    Version 3.0 and later		version 1.2

Introduction | Alphabetic | Specification

Last Edited: Thursday, September 28, 1995

AFV


OpenGL Index of routines in Specification Order

Table of Contents

Chapter 2. Fundamentals

Chapter 3. Rasterization

Chapter 4. Per-Fragment Operations and the Framebuffer

Chapter 5. Special Functions

Chapter 6. State and State Requests

OpenGL on the X Window System(tm)

The OpenGL Utility Library

The Vertex Array Extension

Chapter 2. Fundamentals

glGetError
glGetError
glBegin
glBegin, glEnd
glVertex
glVertex2d, glVertex2dv, glVertex2f, glVertex2fv, glVertex2i, glVertex2iv, glVertex2s, glVertex2sv, glVertex3d, glVertex3dv, glVertex3f, glVertex3fv, glVertex3i, glVertex3iv, glVertex3s, glVertex3sv, glVertex4d, glVertex4dv, glVertex4f, glVertex4fv, glVertex4i, glVertex4iv, glVertex4s, glVertex4sv
glNormal
glNormal3b, glNormal3bv, glNormal3d, glNormal3dv, glNormal3f, glNormal3fv, glNormal3i, glNormal3iv, glNormal3s, glNormal3sv
glColor
glColor3b, glColor3bv, glColor3d, glColor3dv, glColor3f, glColor3fv, glColor3i, glColor3iv, glColor3s, glColor3sv, glColor3ub, glColor3ubv, glColor3ui, glColor3uiv, glColor3us, glColor3usv, glColor4b, glColor4bv, glColor4d, glColor4dv, glColor4f, glColor4fv, glColor4i, glColor4iv, glColor4s, glColor4sv, glColor4ub, glColor4ubv, glColor4ui, glColor4uiv, glColor4us, glColor4usv
glIndex
glIndexd, glIndexdv, glIndexf, glIndexfv, glIndexi, glIndexiv, glIndexs, glIndexsv
glTexCoord
glTexCoord1d, glTexCoord1dv, glTexCoord1f, glTexCoord1fv, glTexCoord1i, glTexCoord1iv, glTexCoord1s, glTexCoord1sv, glTexCoord2d, glTexCoord2dv, glTexCoord2f, glTexCoord2fv, glTexCoord2i, glTexCoord2iv, glTexCoord2s, glTexCoord2sv, glTexCoord3d, glTexCoord3dv, glTexCoord3f, glTexCoord3fv, glTexCoord3i, glTexCoord3iv, glTexCoord3s, glTexCoord3sv, glTexCoord4d, glTexCoord4dv, glTexCoord4f, glTexCoord4fv, glTexCoord4i, glTexCoord4iv, glTexCoord4s, glTexCoord4sv
glEdgeFlag
glEdgeFlag, glEdgeFlagv
glRect
glRectd, glRectdv, glRectf, glRectfv, glRecti, glRectiv, glRects, glRectsv
glViewport
glViewport
glDepthRange
glDepthRange
glMatrixMode
glMatrixMode
glLoadMatrix
glLoadMatrixd, glLoadMatrixf
glMultMatrix
glMultMatrixd, glMultMatrixf
glLoadIdentity
glLoadIdentity
glRotate
glRotated, glRotatef
glTranslate
glTranslated, glTranslatef
glScale
glScaled, glScalef
glFrustum
glFrustum
glOrtho
glOrtho
glPushMatrix
glPushMatrix, glPopMatrix
glEnable
glEnable, glDisable
glTexGen
glTexGend, glTexGendv, glTexGenf, glTexGenfv, glTexGeni, glTexGeniv
glClipPlane
glClipPlane
glRasterPos
glRasterPos2d, glRasterPos2dv, glRasterPos2f, glRasterPos2fv, glRasterPos2i, glRasterPos2iv, glRasterPos2s, glRasterPos2sv, glRasterPos3d, glRasterPos3dv, glRasterPos3f, glRasterPos3fv, glRasterPos3i, glRasterPos3iv, glRasterPos3s, glRasterPos3sv, glRasterPos4d, glRasterPos4dv, glRasterPos4f, glRasterPos4fv, glRasterPos4i, glRasterPos4iv, glRasterPos4s, glRasterPos4sv
glFrontFace
glFrontFace
glMaterial
glMaterialf, glMaterialfv, glMateriali, glMaterialiv
glLight
glLightf, glLightfv, glLighti, glLightiv
glLightModel
glLightModelf, glLightModelfv, glLightModeli, glLightModeliv
glColorMaterial
glColorMaterial
glShadeModel
glShadeModel

Chapter 3. Rasterization

glPointSize
glPointSize
glLineWidth
glLineWidth
glLineStipple
glLineStipple
glCullFace
glCullFace
glPolygonStipple
glPolygonStipple
glPolygonMode
glPolygonMode
glPixelStore
glPixelStoref, glPixelStorei
glPixelTransfer
glPixelTransferf, glPixelTransferi
glPixelMap
glPixelMapfv, glPixelMapuiv, glPixelMapusv
glDrawPixels
glDrawPixels
glPixelZoom
glPixelZoom
glBitmap
glBitmap
glTexImage2D
glTexImage2D
glTexImage1D
glTexImage1D
glTexParameter
glTexParameterf, glTexParameterfv, glTexParameteri, glTexParameteriv
glTexEnv
glTexEnvf, glTexEnvfv, glTexEnvi, glTexEnviv
glFog
glFogf, glFogfv, glFogi, glFogiv

Chapter 4. Per-Fragment Operations and the Framebuffer

glScissor
glScissor
glAlphaFunc
glAlphaFunc
glStencilFunc
glStencilFunc
glStencilOp
glStencilOp
glDepthFunc
glDepthFunc
glBlendFunc
glBlendFunc
glLogicOp
glLogicOp
glDrawBuffer
glDrawBuffer
glIndexMask
glIndexMask
glColorMask
glColorMask
glDepthMask
glDepthMask
glStencilMask
glStencilMask
glClear
glClear
glClearColor
glClearColor
glClearIndex
glClearIndex
glClearDepth
glClearDepth
glClearStencil
glClearStencil
glClearAccum
glClearAccum
glAccum
glAccum
glReadPixels
glReadPixels
glReadBuffer
glReadBuffer
glCopyPixels
glCopyPixels

Chapter 5. Special Functions

glMap1
glMap1d, glMap1f
glMap2
glMap2d, glMap2f
glEvalCoord
glEvalCoord1d, glEvalCoord1dv, glEvalCoord1f, glEvalCoord1fv, glEvalCoord2d, glEvalCoord2dv, glEvalCoord2f, glEvalCoord2fv
glMapGrid
glMapGrid1d, glMapGrid1f, glMapGrid2d, glMapGrid2f
glEvalMesh
glEvalMesh1, glEvalMesh2
glEvalPoint
glEvalPoint1, glEvalPoint2
glInitNames
glInitNames
glPushName
glPushName, glPopName
glLoadName
glLoadName
glRenderMode
glRenderMode
glSelectBuffer
glSelectBuffer
glFeedbackBuffer
glFeedbackBuffer
glPassThrough
glPassThrough
glNewList
glNewList, glEndList
glCallList
glCallList
glCallLists
glCallLists
glListBase
glListBase
glGenLists
glGenLists
glIsList
glIsList
glDeleteLists
glDeleteLists
glFlush
glFlush
glFinish
glFinish
glHint
glHint

Chapter 6. State and State Requests

glGet
glGetBooleanv, glGetDoublev, glGetFloatv, glGetIntegerv
glIsEnabled
glIsEnabled
glGetClipPlane
glGetClipPlane
glGetLight
glGetLightfv, glGetLightiv
glGetMaterial
glGetMaterialfv, glGetMaterialiv
glGetTexEnv
glGetTexEnvfv, glGetTexEnviv
glGetTexGen
glGetTexGendv, glGetTexGenfv, glGetTexGeniv
glGetTexParameter
glGetTexParameterfv, glGetTexParameteriv
glGetTexLevelParameter
glGetTexLevelParameterfv, glGetTexLevelParameteriv
glGetPixelMap
glGetPixelMapfv, glGetPixelMapuiv, glGetPixelMapusv
glGetMap
glGetMapdv, glGetMapfv, glGetMapiv
glGetTexImage
glGetTexImage
glGetPolygonStipple
glGetPolygonStipple
glGetString
glGetString
glPushAttrib
glPushAttrib, glPopAttrib

OpenGL on the X Window System(tm)

glXIntro
Introduction
glXQueryExtension
glXQueryExtension
glXQueryVersion
glXQueryVersion
glXGetConfig
glXGetConfig
glXChooseVisual
glXChooseVisual
glXCreateGLXPixmap
glXCreateGLXPixmap
glXDestroyGLXPixmap
glXDestroyGLXPixmap
glXCreateContext
glXCreateContext
glXIsDirect
glXIsDirect
glXDestroyContext
glXDestroyContext
glXCopyContext
glXCopyContext
glXMakeCurrent
glXMakeCurrent
glXGetCurrentContext
glXGetCurrentContext
glXGetCurrentDrawable
glXGetCurrentDrawable
glXWaitGL
glXWaitGL
glXWaitX
glXWaitX
glXSwapBuffers
glXSwapBuffers
glXUseXFont
glXUseXFont

The OpenGL Utility Library

See notes on the new GLU tesselator

gluScaleImage
gluScaleImage
gluBuild1DMipmaps
gluBuild1DMipmaps
gluBuild2DMipmaps
gluBuild2DMipmaps
gluOrtho2D
gluOrtho2D
gluPerspective
gluPerspective
gluLookAt
gluLookAt
gluPickMatrix
gluPickMatrix
gluProject
gluProject
gluUnProject
gluUnProject
gluNewQuadric
gluNewQuadric
gluDeleteQuadric
gluDeleteQuadric
gluQuadricCallback
gluQuadricCallback
gluQuadricNormals
gluQuadricNormals
gluQuadricTexture
gluQuadricTexture
gluQuadricOrientation
gluQuadricOrientation
gluQuadricDrawStyle
gluQuadricDrawStyle
gluSphere
gluSphere
gluCylinder
gluCylinder
gluDisk
gluDisk
gluPartialDisk
gluPartialDisk
gluNewNurbsRenderer
gluNewNurbsRenderer
gluDeleteNurbsRenderer
gluDeleteNurbsRenderer
gluNurbsCallback
gluNurbsCallback
gluBeginCurve
gluBeginCurve, gluEndCurve
gluNurbsCurve
gluNurbsCurve
gluBeginSurface
gluBeginSurface, gluEndSurface
gluNurbsSurface
gluNurbsSurface
gluBeginTrim
gluBeginTrim, gluEndTrim
gluPwlCurve
gluPwlCurve
gluNurbsProperty
gluNurbsProperty
gluLoadSamplingMatrices
gluLoadSamplingMatrices
gluGetNurbsProperty
gluGetNurbsProperty
gluErrorString
gluErrorString

The GLU tesselator for GLU versions 1.0 and 1.1:

gluNewTess
gluNewTess
gluDeleteTess
gluDeleteTess
gluTessCallback
gluTessCallback
gluBeginPolygon
gluBeginPolygon, gluEndPolygon
gluTessVertex
gluTessVertex
gluNextContour
gluNextContour

The GLU tesselator for GLU version 1.2 and later:

gluNewTess
gluNewTess
gluDeleteTess
gluDeleteTess
gluTessCallback
gluTessCallback
gluTessBeginPolygon
gluTessBeginPolygon
gluTessEndPolygon
gluTessEndPolygon
gluTessVertex
gluTessVertex
gluTessBeginContour
gluBeginContour, gluEndContour
gluTessProperty
gluTessProperty
gluGetTessProperty
gluGetTessProperty
gluTessNormal
gluTessNormal
gluBeginPolygon
gluBeginPolygon, gluEndPolygon (Obsolete)
gluNextContour
gluNextContour (Obsolete)

The Vertex Array Extension

glArrayElementEXT
glArrayElementEXT
glColorPointerEXT
glColorPointerEXT
glDrawArraysEXT
glDrawArraysEXT
glEdgeFlagPointerEXT
glEdgeFlagPointerEXT
glGetPointervEXT
glGetPointervEXT
glIndexPointerEXT
glIndexPointerEXT
glNormalPointerEXT
glNormalPointerEXT
glTexCoordPointerEXT
glTexCoordPointerEXT
glVertexPointerEXT
glVertexPointerEXT


OpenGL Index of routines in Alphabetic Order

Contents

glA glB glC glD glE glF glG glH glI glL glM glN glO glP glR glS glT glV glX glu

OpenGL Index in Alphabetic Order

glA...
glAccum
glAlphaFunc
glArrayElementEXT
glB...
glBegin
glBitmap
glBlendFunc
glC...
glCallList
glCallLists
glClear
glClearAccum
glClearColor
glClearDepth
glClearIndex
glClearStencil
glClipPlane
glColor3b, glColor3bv, glColor3d, glColor3dv, glColor3f, glColor3fv, glColor3i, glColor3iv, glColor3s, glColor3sv, glColor3ub, glColor3ubv, glColor3ui, glColor3uiv, glColor3us, glColor3usv, glColor4b, glColor4bv, glColor4d, glColor4dv, glColor4f, glColor4fv, glColor4i, glColor4iv, glColor4s, glColor4sv, glColor4ub, glColor4ubv, glColor4ui, glColor4uiv, glColor4us, glColor4usv
glColorMask
glColorMaterial
glColorPointerEXT
glCopyPixels
glCullFace
glD...
glDeleteLists
glDepthFunc
glDepthMask
glDepthRange
glDisable
glDrawArraysEXT
glDrawBuffer
glDrawPixels
glE...
glEdgeFlag
glEdgeFlagPointerEXT
glEdgeFlagv
glEnable
glEnd
glEndList
glEvalCoord1d, glEvalCoord1dv, glEvalCoord1f, glEvalCoord1fv, glEvalCoord2d, glEvalCoord2dv, glEvalCoord2f, glEvalCoord2fv
glEvalMesh1, glEvalMesh2
glEvalPoint1, glEvalPoint2
glF...
glFeedbackBuffer
glFinish
glFlush
glFogf, glFogfv, glFogi, glFogiv
glFrontFace
glFrustum
glG...
glGenLists
glGetBooleanv
glGetClipPlane
glGetDoublev
glGetError
glGetFloatv, glGetIntegerv
glGetLightfv, glGetLightiv
glGetMapdv, glGetMapfv, glGetMapiv
glGetMaterialfv, glGetMaterialiv
glGetPixelMapfv, glGetPixelMapuiv, glGetPixelMapusv
 glGetPointervEXT
glGetPolygonStipple
glGetString
glGetTexEnvfv, glGetTexEnviv
glGetTexGendv, glGetTexGenfv, glGetTexGeniv
glGetTexImage
glGetTexLevelParameterfv, glGetTexLevelParameteriv
glGetTexParameterfv, glGetTexParameteriv
glH...
glHint
glI...
glIndexMask
glIndexPointerEXT
glIndexd, glIndexdv, glIndexf, glIndexfv, glIndexi, glIndexiv, glIndexs, glIndexsv
glInitNames
glIsEnabled
glIsList
glL...
glLightModelf, glLightModelfv, glLightModeli, glLightModeliv
glLightf, glLightfv, glLighti, glLightiv
glLineStipple
glLineWidth
glListBase
glLoadIdentity
glLoadMatrixd, glLoadMatrixf
glLoadName
glLogicOp
glM...
glMap1d, glMap1f
glMap2d, glMap2f
glMapGrid1d, glMapGrid1f, glMapGrid2d, glMapGrid2f
glMaterialf, glMaterialfv, glMateriali, glMaterialiv
glMatrixMode
glMultMatrixd, glMultMatrixf
glN...
glNewList
glNormal3b, glNormal3bv, glNormal3d, glNormal3dv, glNormal3f, glNormal3fv, glNormal3i, glNormal3iv, glNormal3s, glNormal3sv
glNormalPointerEXT
glO...
glOrtho
glP...
glPassThrough
glPixelMapfv, glPixelMapuiv, glPixelMapusv
glPixelStoref, glPixelStorei
glPixelTransferf, glPixelTransferi
glPixelZoom
glPointSize
glPolygonMode
glPolygonStipple
glPopAttrib
glPopMatrix
glPopName
glPushAttrib
glPushMatrix
glPushName
glR...
glRasterPos2d, glRasterPos2dv, glRasterPos2f, glRasterPos2fv, glRasterPos2i, glRasterPos2iv, glRasterPos2s, glRasterPos2sv, glRasterPos3d, glRasterPos3dv, glRasterPos3f, glRasterPos3fv, glRasterPos3i, glRasterPos3iv, glRasterPos3s, glRasterPos3sv, glRasterPos4d, glRasterPos4dv, glRasterPos4f, glRasterPos4fv, glRasterPos4i, glRasterPos4iv, glRasterPos4s, glRasterPos4sv
glReadBuffer
glReadPixels
glRectd, glRectdv, glRectf, glRectfv, glRecti, glRectiv, glRects, glRectsv
glRenderMode
glRotated glRotatef
glS...
glScaled, glScalef
glScissor
glSelectBuffer
glShadeModel
glStencilFunc
glStencilMask
glStencilOp
glT...
glTexCoord1d, glTexCoord1dv, glTexCoord1f, glTexCoord1fv, glTexCoord1i, glTexCoord1iv, glTexCoord1s, glTexCoord1sv, glTexCoord2d, glTexCoord2dv, glTexCoord2f, glTexCoord2fv, glTexCoord2i, glTexCoord2iv, glTexCoord2s, glTexCoord2sv, glTexCoord3d, glTexCoord3dv, glTexCoord3f, glTexCoord3fv, glTexCoord3i, glTexCoord3iv, glTexCoord3s, glTexCoord3sv, glTexCoord4d, glTexCoord4dv, glTexCoord4f, glTexCoord4fv, glTexCoord4i, glTexCoord4iv, glTexCoord4s, glTexCoord4sv
glTexCoordPointerEXT,
glTexEnvf, glTexEnvfv, glTexEnvi, glTexEnviv,
glTexGend, glTexGendv, glTexGenf, glTexGenfv, glTexGeni, glTexGeniv
glTexImage1D
glTexImage2D
glTexParameterf, glTexParameterfv, glTexParameteri, glTexParameteriv
glTranslated, glTranslatef
glV...
glVertex2d, glVertex2dv, glVertex2f, glVertex2fv, glVertex2i, glVertex2iv, glVertex2s, glVertex2sv, glVertex3d, glVertex3dv, glVertex3f, glVertex3fv, glVertex3i, glVertex3iv, glVertex3s, glVertex3sv, glVertex4d, glVertex4dv, glVertex4f, glVertex4fv, glVertex4i, glVertex4iv, glVertex4s, glVertex4sv
glVertexPointerEXT
glViewport
glX...
glXChooseVisual
glXCopyContext
glXCreateContext
glXCreateGLXPixmap
glXDestroyContext
glXDestroyGLXPixmap
glXGetConfig
glXGetCurrentContext
glXGetCurrentDrawable
glXIsDirect
glXMakeCurrent
glXQueryExtension
glXQueryVersion
glXSwapBuffers
glXUseXFont
glXWaitGL
glXWaitX
glu...
See notes on the new GLU tesselator

gluBeginCurve
gluBeginPolygon (GLU versions 1.0 and 1.1)
gluBeginPolygon (GLU version 1.2 and later)
gluBeginSurface
gluBeginTrim
gluBuild1DMipmaps
gluBuild2DMipmaps
gluCylinder
gluDeleteNurbsRenderer
gluDeleteQuadric
gluDeleteTess (GLU versions 1.0 and 1.1)
gluDeleteTess (GLU version 1.2 and later)
gluDisk
gluEndCurve
gluEndPolygon (GLU versions 1.0 and 1.1)
gluEndPolygon (GLU version 1.2 and later)
gluEndSurface
gluEndTrim
gluErrorString
gluGetNurbsProperty
gluGetTessProperty (GLU version 1.2 and later)
gluLoadSamplingMatrices
gluLookAt
gluNewNurbsRenderer
gluNewQuadric
gluNewTess (GLU versions 1.0 and 1.1)
gluNewTess (GLU version 1.2 and later)
gluNextContour (GLU versions 1.0 and 1.1)
gluNextContour (GLU version 1.2 and later)
gluNurbsCallback
gluNurbsCurve
gluNurbsProperty
gluNurbsSurface
gluOrtho2D
gluPartialDisk
gluPerspective
gluPickMatrix
gluProject
gluPwlCurve
gluQuadricCallback
gluQuadricDrawStyle
gluQuadricNormals
gluQuadricOrientation
gluQuadricTexture
gluScaleImage
gluSphere
gluTessBeginContour (GLU version 1.2 and later)
gluTessBeginPolygon (GLU version 1.2 and later)
gluTessCallback (GLU versions 1.0 and 1.1)
gluTessCallback (GLU version 1.2 and later)
gluTessEndContour (GLU version 1.2 and later)
gluTessEndPolygon (GLU version 1.2 and later)
gluTessNormal (GLU version 1.2 and later)
gluTessProperty (GLU version 1.2 and later)
gluTessVertex (GLU versions 1.0 and 1.1)
gluTessVertex (GLU version 1.2 and later)
gluUnProject

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


Tutoriels sur la programmation OpenGL moderne (ogldev en français)

  • Introduction
  • Tutoriels
  • Source

Introduction

Cette série de tutoriels en C++ vous permettra d'apprendre l'OpenGL moderne (OpenGL > 3, pipeline dynamique). Pour exécuter ceux-ci, vous devez donc avoir un GPU compatible avec OpenGL 3.3 ou plus. Ils ont été développés sous Linux avec NetBeans mais vous pouvez les compiler simplement avec 'make'. Sachant que l'auteur utilise GLEW, vous devez l'installer pour pouvoir compiler les tutoriels. Pour les utilisateurs sous Windows, un fichier de projet pour Visual Studio est aussi disponible.

Tutoriels

  • Ouvrir une fenêtre
  • Hello point
  • Le premier triangle
  • Les shaders
  • Les variables uniformes
  • Les transformations : translation
  • Les transformations : rotation
  • Les transformations : mise à l'échelle
  • Interpolation
  • Dessins indexés
  • Concaténation des transformations
  • Projection en perspective
  • Espace caméra
  • Contrôle de la caméra (au clavier)
  • Contrôle de la caméra (à la souris)
  • Application basique de texture
  • Éclairage ambiant
  • Lumière diffuse
  • Lumière spéculaire
  • Lumières ponctuelles
  • Projecteurs
  • Charger des modèles avec Assimp
  • Shadow-mapping (première partie)

Source

Ces tutoriels sont accompagnés de code d'exemple que vous pouvez télécharger ici.

Cette page est valide HTML 5 et CSS 3

Copyright © 2014 - 2016 Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite - Contacter par email
  • Nous contacter
  • Participez
  • Hébergement
  • Informations légales

Copyright © 2000-2016 - www.developpez.com


OpenGL - Tutoriel 1 - Ouvrir une fenêtre

Tutoriel 1 - Ouvrir une fenêtre

Table des matières Plier Déplier

  • Navigation
  • II. Contexte
  • III. Explication du code
  • IV. Sources
  • V. Remerciements
  • Navigation

La spécification d'OpenGL ne définit pas de fonctions particulières pour manipuler des fenêtres. Les systèmes modernes de fenêtrage supportant OpenGL incluent généralement un sous-système fournissant la liaison entre un contexte OpenGL et le système de fenêtrage. Dans le système X Window, cette interface est appelée GLX. Microsoft fournit WGL (prononcé Wigueul) pour Windows et MacOS a CGL. Travailler directement avec ces interfaces afin de créer une fenêtre pour gérer l'affichage est généralement un travail fastidieux, c'est pourquoi nous allons utiliser une bibliothèque de haut niveau qui fera abstraction de ces détails.
La bibliothèque que nous allons utiliser ici est appelée « OpenGL utility library », ou GLUT. Elle fournit des fonctions simplifiées pour la gestion de fenêtres ainsi qu'une gestion évènementielle, une gestion des E/S et quelques autres services. De plus, GLUT est multiplateforme, ce qui facilite le portage.
Les alternatives à GLUT sont, entre autres, SDL ou GLFW.

III. Explication du code▲

Sélectionnez
glutInit(&argc, argv);

Cet appel initialise GLUT.
Les paramètres peuvent être fournis directement depuis la ligne de commande et inclure des options utiles du type « -sync » ou « -gldebug » qui désactive la nature asynchrone de X et vérifie automatiquement les erreurs GL et les affiche (respectivement).

Sélectionnez
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);

Ici nous configurons quelques options de GLUT.
GLUT_DOUBLE active le « double buffering » (dessiner dans un tampon en arrière-plan pendant qu'un autre tampon est affiché).
GLUT_RGBA définit le format des pixels du tampon de couleurs où sont appliqués les rendus (par exemple : l'écran).
On active généralement ces deux options ainsi que d'autres comme nous le verrons plus tard.

Sélectionnez
glutInitWindowSize(1024, 768);
glutInitWindowPosition(100, 100);
glutCreateWindow("Tutorial 01");

Ces appels définissent les paramètres de la fenêtre et la créent. Vous avez aussi la possibilité de spécifier le titre de la fenêtre.

Sélectionnez
glutDisplayFunc(RenderSceneCB);

Comme nous travaillons dans un système fenêtré, les interactions avec le programme vont principalement se faire au travers de callbacks sur des évènements. GLUT prend en charge les interactions avec le système de fenêtres sous-jacent et nous fournit quelques callbacks.
Ici nous en utilisons juste un : le callback « principal » pour effectuer le rendu d'une image. Cette fonction est appelée continuellement par la boucle interne de GLUT.

Sélectionnez
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

C'est notre première rencontre avec le concept d'état en OpenGL.
L'idée d'un état est que le rendu est une tâche tellement complexe qu'elle ne peut être traitée dans un seul appel de fonction avec quelques paramètres (et une fonction correctement conçue ne devrait jamais recevoir beaucoup de paramètres).
Il faut spécifier les shaders, tampons et divers indicateurs qui affectent la manière dont le rendu s'effectue. De plus vous voudrez souvent garder la même configuration au travers de plusieurs opérations de rendu (exemple : si vous ne désactivez jamais le test de profondeur, il n'y a pas lieu de le spécifier à chaque rendu). C'est pourquoi la plus grande partie de la configuration des opérations de rendu s'effectue en définissant des indicateurs et des valeurs dans la machine à états d'OpenGL. Les rendus eux-mêmes sont habituellement limités aux quelques paramètres relatant du nombre de sommets à dessiner et leur décalage de départ.
Après l'appel d'une fonction de changement d'état, cette nouvelle configuration reste inchangée jusqu'au prochain appel de cette même fonction avec une valeur différente. L'appel ci-dessus définit la couleur qui sera utilisée lors du vidage du tampon d'image (décrit plus tard). La couleur a quatre composantes (RGBA) et chaque composante est normalisée entre 0.0 et 1.0.

Sélectionnez
glutMainLoop();

Cet appel donne le contrôle à GLUT qui lance sa boucle interne. Dans cette boucle GLUT écoute les évènements du système de fenêtres et les passe aux callbacks que nous avons définies. Dans notre cas, GLUT va seulement appeler la fonction enregistrée comme callback d'affichage (RenderSceneCB) pour nous donner l'occasion de dessiner une image.

Sélectionnez
glClear(GL_COLOR_BUFFER_BIT);
glutSwapBuffers();

La seule chose que nous faisons dans notre fonction de rendu est de vider le tampon d'image (en utilisant la couleur spécifiée au-dessus - essayez de la changer). Le second appel dit à GLUT d'échanger les rôles du « backbuffer » et du « frontbuffer ». Dans le prochain appel du callback de rendu, nous rendrons dans l'actuel « frontbuffer » et l'actuel « backbuffer » sera affiché.

Image non disponible
Résultat

IV. Sources▲

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérez les sources

V. Remerciements▲

Merci à Etay Meiri de nous permettre de traduire son tutoriel.

Merci à LittleWhite pour ses corrections et à ClaudeLELOUP pour sa relecture.

Navigation▲

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -
  • Nous contacter
  • Participez
  • Hébergement
  • Informations légales

Copyright © 2000-2016 - www.developpez.com


OpenGL - Tutoriel 2 - Hello Point

Tutoriel 2 : Hello Point

Table des matières Plier Déplier

  • II. Contexte
    • II-A. Installation de GLEW
  • III. Explication du code
  • IV. Sources
  • V. Remerciements

C'est notre première rencontre avec la bibliothèque GLEW, « OpenGL Extension Wrangler Library ». GLEW vous aide à contourner le mal de crâne qui vous attend si vous voulez gérer les extensions dans OpenGL. Une fois initialisé, il récupère les extensions disponibles sur votre plateforme, les charge et fournit un accès facile via un simple fichier d'en-tête.

Dans ce tutoriel, nous allons voir l'utilisation des « Vertex Buffer Objects » (VBO) pour la première fois. Comme son nom l'indique, ils sont utilisés pour stocker des sommets.

Les objets du monde 3D que vous essayez de virtualiser, que ce soit des monstres, des châteaux ou un simple cube tournant, sont toujours construits en connectant des groupes de sommets ensemble.

Les VBO sont la manière la plus efficace de charger des sommets sur le GPU. Ce sont des tampons qui sont stockés sur la mémoire vidéo et ils fournissent le plus faible temps d'accès au GPU, ils sont donc vivement recommandés.

Ce tutoriel et le suivant sont les seuls de cette série qui s'appuient sur le pipeline fixe plutôt que sur le pipeline programmable.

En réalité, aucune transformation ne va avoir lieu dans ces deux tutoriels. Nous nous appuyons simplement sur la manière dont le flux de données transite dans le pipeline.

Une étude plus approfondie du pipeline suivra dans les prochains tutoriels, mais pour l'instant, il suffit de comprendre qu'avant d'atteindre le rasterizer (qui dessine effectivement les points, lignes et triangles en utilisant les coordonnées écran), les sommets visibles ont leurs coordonnées X, Y et Z entre les bornes [-1.0, 1.0]. Le rasterizer fait correspondre les coordonnées à l'espace écran (exemple : si la largeur de l'écran est 1024 alors la coordonnée 0.0 correspond à 0 et 1.0 correspond à 1023).

Finalement, le rasterizer dessine les primitives selon la topologie spécifiée dans la fonction de dessin (voir l'explication de code ci-dessous). Comme nous n'attachons pas de shader au pipeline, nos sommets ne subissent aucune transformation.

Ça signifie que nous avons juste à leur donner une valeur entre les bornes précédentes afin de les rendre visibles. En fait, définir zéro pour X et Y place le sommet au milieu des deux axes et par là même au milieu de l'écran.

GLEW est disponible sur le site http://glew.sourceforge.net/.

La plupart des distributions Linux fournissent un paquet pour lui.

Sur Ubuntu vous pouvez l'installer en utilisant la ligne de commande suivante :

apt-get install libglew1.6 libglew1.6-dev

III. Explication du code▲

Sélectionnez
#include <GL/glew.h>

Ici nous incluons l'unique fichier d'en-tête de GLEW.

Si vous incluez d'autres fichiers d'en-têtes OpenGL, vous devez faire attention à inclure ce fichier d'en-tête avant les autres sinon GLEW va s'en plaindre. Afin de lier le programme avec GLEW, vous devez ajouter « -lGLEW » au makefile.

Sélectionnez
#include "math_3d.h"

Dans ce tutoriel nous commençons à utiliser des structures d'aides telles que les vecteurs. Nous étofferons ce fichier d'en-tête au fur et à mesure de nos avancées.

Sélectionnez
GLenum res = glewInit();
if (res != GLEW_OK)
{
    fprintf(stderr, "Error: '%s'\n", glewGetErrorString(res));
    return 1;
}

Ici nous initialisons GLEW et vérifions les erreurs éventuelles. Cela doit être fait après l'initialisation de GLUT.

Sélectionnez
Vector3f Vertices[1];
Vertices[0] = Vector3f(0.0f, 0.0f, 0.0f);

Nous créons un tableau contenant une structure Vector3f (type défini dans math_3d.h) et initialisons XYZ à zéro. Ainsi le point apparaîtra au milieu de l'écran.

Sélectionnez
GLuint VBO;

Nous allouons un GLuint en variable globale au programme pour stocker l'identifiant du VBO.

Vous verrez par la suite que la majorité (si pas tous) des objets OpenGL sont représentés par une variable de type GLuint.

Sélectionnez
glGenBuffers(1, &VBO);

OpenGL définit plusieurs fonctions glGen* pour créer des objets de différents types.

Elles prennent souvent deux paramètres :

  • le premier spécifie le nombre d'objets à créer ;
  • le second est l'adresse d'un tableau de GLuint pour stocker les identifiants alloués par le pilote (faites attention à avoir un tableau assez grand pour récupérer le résultat).

Les futurs appels à cette fonction ne généreront pas les mêmes identifiants à moins qu'ils aient d'abord été détruits avec glDeleteBuffers.

Notez qu'à ce stade vous ne spécifiez pas ce que vous avez l'intention de faire avec les tampons et donc ils peuvent être considérés comme « génériques ». Leur spécification est la tâche de la fonction suivante.

Sélectionnez
glBindBuffer(GL_ARRAY_BUFFER, VBO);

OpenGL a une manière plutôt unique d'utiliser les identifiants.

Dans de nombreuses bibliothèques, l'identifiant est simplement passé à une fonction correspondante et l'action est effectuée sur cet identifiant. En OpenGL, nous attachons l'identifiant à un nom de cible et effectuons ensuite les commandes sur cette cible. Ces commandes affectent l'identifiant attaché jusqu'à ce qu'un autre soit attaché à sa place ou que la fonction ci-dessus soit appelée avec 0 comme identifiant.

La cible GL_ARRAY_BUFFER signifie que le tampon va contenir un tableau de sommets.

Une autre cible utile est GL_ELEMENT_ARRAY_BUFFER qui signifie que le tampon contient les indices des sommets d'un autre tampon.

D'autres cibles sont aussi disponibles et nous verrons leur utilité dans de futurs tutoriels.

Sélectionnez
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), Vertices, GL_STATIC_DRAW);

Après avoir attaché notre objet, nous le remplissons de données.

L'appel ci-dessus prend le nom de la cible (le même que celui utilisé pour l'attachement), la taille des données en octets, l'adresse du tableau de sommets et un indicateur indiquant le modèle d'utilisation de ces données.

Comme nous n'allons pas changer le contenu du tampon, nous spécifions GL_STATIC_DRAW. L'opposé serait GL_DYNAMIC_DRAW.

Bien que ce ne soit qu'un conseil pour OpenGL, il est bon de réfléchir à l'indicateur correct à utiliser. Le pilote peut s'appuyer dessus pour des stratégies d'optimisation (comme la meilleure place en mémoire pour stocker le tampon).

Sélectionnez
glEnableVertexAttribArray(0);

Dans le tutoriel sur les shaders, vous verrez que les attributs de sommet utilisés par le shader (position, normale…) ont un indice associé, qui vous permet de créer un lien entre les données du programme C/C++ et le nom de l'attribut au sein du shader.

De plus vous pouvez aussi activer chaque indice d'attribut de sommet.

Dans ce tutoriel, nous n'utilisons pas encore de shader, mais la position du sommet que nous avons chargé dans le tampon est traitée comme l'indice 0 d'attribut de sommet dans le pipeline fixe (qui devient actif s'il n'y a pas de shader lié).

Vous devez activer chaque attribut de sommet, sinon les données associées ne seront pas accessibles dans le pipeline.

Sélectionnez
glBindBuffer(GL_ARRAY_BUFFER, VBO);

Ici, nous attachons encore notre tampon comme nous nous préparons à appeler la fonction de dessin.

Dans ce petit programme, nous n'avons qu'un seul tampon de sommets donc faire cet appel à chaque image est redondant, mais dans les programmes plus complexes, où il y a de multiples tampons pour stocker vos divers modèles, vous devez mettre à jour l'état du pipeline avec le tampon que vous avez l'intention d'utiliser.

Sélectionnez
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);

Cet appel dit au pipeline comment interpréter les données dans le tampon.

Le premier paramètre spécifie l'indice de l'attribut. Dans notre cas, nous savons que c'est zéro par défaut, mais quand nous commencerons à utiliser les shaders, nous aurons besoin soit de le spécifier explicitement, soit de le demander.

Le second paramètre est le nombre de composantes dans l'attribut (trois pour X, Y et Z).

Le troisième paramètre est le type de données de toutes les composantes.

Le paramètre suivant indique si nous voulons que notre attribut soit normalisé avant son utilisation dans le pipeline. Dans notre cas, nous voulons que nos données passent sans être modifiées.

Le cinquième paramètre (appelé « stride ») est le nombre d'octets entre deux instances de l'attribut dans le tampon. Quand il n'y a qu'un seul attribut (exemple : le tampon contient uniquement des positions) et que les données sont contiguës, nous passons zéro. Si nous avons un tableau de structures qui contiennent une position et une normale (chacune étant un vecteur de trois flottants), nous passerons la taille de la structure en octets (6 * 4 = 24).

Le dernier paramètre est utile dans le cas de l'exemple précédent. Nous devons spécifier le décalage à l'intérieur de la structure où le pipeline va trouver notre attribut. Dans le cas de la structure avec la position et la normale, le décalage de la position est zéro alors que le décalage de la normale est 12.

Sélectionnez
glDrawArrays(GL_POINTS, 0, 1);

Enfin, nous appelons la fonction de dessin de la géométrie.

Toutes les commandes que nous avons vues jusqu'ici sont importantes, mais elles ne font que préparer le terrain pour l'appel de cette fonction. C'est là que le GPU commence réellement son travail. Il va combiner les paramètres de la fonction de dessin avec l'état qui a été construit jusqu'à maintenant et rendre le résultat sur l'écran.

OpenGL fournit plusieurs types de fonctions de dessin et chacune est propre à un cas particulier. En général il est possible de les diviser en deux catégories :

  • les dessins ordonnés ;
  • les dessins indicés.

Les dessins ordonnés sont les plus simples. Le GPU parcourt le tampon de sommets, sommet par sommet et les interprète selon la topologie spécifiée dans la fonction de dessin. Par exemple, si vous spécifiez GL_TRIANGLES alors les sommets 0 à 2 deviennent le premier triangle, 3 à 5 le second…

Si vous voulez que le même sommet apparaisse dans plus d'un triangle, vous devez le spécifier autant de fois dans le tampon de sommets, ce qui est un gâchis de place.

Les dessins indicés sont plus complexes et impliquent un tampon supplémentaire : le tampon d'indices. Le tampon d'indices contient les indices des sommets dans le tampon de sommets. Le GPU scanne le tampon d'indices et de la même manière que dans la description précédente les indices 0 à 2 forment le premier triangle…

Si vous voulez que le même sommet apparaisse dans deux triangles, vous avez juste à spécifier son indice deux fois dans le tampon d'indices. Le tampon de sommets a besoin de n'en contenir qu'une seule copie.

Les dessins indicés sont plus communs dans les jeux, car la majorité des modèles sont créés à partir de triangles qui représentent une surface (peau d'une personne, mur de château…) avec beaucoup de partages de sommets entre eux.

Dans ce tutoriel, nous utilisons la fonction de dessin la plus simple : glDrawArrays. C'est un dessin ordonné où il n'y a pas de tampon d'indices.

Nous spécifions la topologie à « points » ce qui signifie que chaque sommet est un point.

Le paramètre suivant est l'indice du premier sommet à dessiner. Dans notre cas, nous voulons commencer au début du tampon donc nous spécifions zéro, mais ça nous permet de stocker plusieurs modèles dans le même tampon et choisir celui à afficher selon le décalage dans le tampon.

Le dernier paramètre est le nombre de sommets à dessiner.

Sélectionnez
glDisableVertexAttribArray(0);

C'est une bonne pratique de désactiver chaque attribut de sommet quand il n'est pas immédiatement utilisé.

Le laisser activé quand un shader ne l'utilise pas est un moyen sûr de rencontrer des problèmes.

Image non disponible
Résultat

IV. Sources▲

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérez les sources

V. Remerciements▲

Merci à Etay Meiri de nous permettre de traduire son tutoriel.

Merci à LittleWhite pour ses corrections, à Torgar ainsi qu'à ClaudeLELOUP pour leur relecture.

Navigation▲

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2013 ogldev. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -
  • Nous contacter
  • Participez
  • Hébergement
  • Informations légales

Copyright © 2000-2016 - www.developpez.com


OpenGL - Tutoriel 3 - Premier triangle

Tutoriel 3 : Premier triangle

Table des matières Plier Déplier

  • II. Contexte
  • III. Explication du code
  • IV. Sources
  • V. Remerciements

Ce tutoriel est très court. Nous élargissons juste le tutoriel précédent afin d'afficher un triangle.

Dans ce tutoriel nous allons, une nouvelle fois, nous appuyer sur la boîte normalisée. Les sommets visibles doivent être dans la boîte pour que la transformation de la vue les fasse correspondre à des coordonnées visibles dans la fenêtre. En regardant le long de l'axe Z négatif, cette boîte ressemble à ça :

Image non disponible

Le point (-1.0, 1.0) correspond au coin bas gauche de la fenêtre, (-1.0, 1.0) correspond au coin haut gauche et ainsi de suite. Si vous étirez la position d'un des sommets hors de cette boîte, le triangle sera coupé et vous n'en verrez qu'une partie.

Sélectionnez
Vector3f Vertices[3];
Vertices[0] = Vector3f(-1.0f, -1.0f, 0.0f);
Vertices[1] = Vector3f(1.0f, -1.0f, 0.0f);
Vertices[2] = Vector3f(0.0f, 1.0f, 0.0f);

Nous avons agrandi le tableau afin qu'il contienne trois sommets.

Sélectionnez
glDrawArrays(GL_TRIANGLES, 0, 3);

Deux changements ont été apportés à la fonction de dessin : nous dessinons des triangles au lieu de points et nous dessinons trois sommets au lieu d'un seul.

IV. Sources▲

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérez les sources

V. Remerciements▲

Merci à Etay Meiri de nous permettre de traduire son tutoriel.

Navigation▲

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 ogldev. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -
  • Nous contacter
  • Participez
  • Hébergement
  • Informations légales

Copyright © 2000-2016 - www.developpez.com


OpenGL - Tutoriel 4 - Shaders

Tutoriel 4 - Shaders

Table des matières Plier Déplier

  • II. Contexte
  • III. Explication du code
  • IV. Sources
  • V. Remerciements

À partir de ce tutoriel, chaque effet et technique que nous implémenterons le sera en utilisant les shaders.
Les shaders sont la méthode moderne pour faire des graphiques 3D.
D'une certaine manière vous pourriez prétendre que c'est un retour en arrière, en effet la plupart des fonctionnalités 3D qui étaient fournies par le pipeline fixe et ne nécessitaient du développeur que la définition de paramètres de configuration (attributs de lumières, rotations…), doivent maintenant être implémentées par le développeur (via les shaders).
Cependant cette programmation permet une plus grande flexibilité et innovation.

Le pipeline programmable d'OpenGL peut être visualisé comme suit :

Image non disponible

Le processeur de sommets exécute le vertex shader sur chaque sommet qui passe à travers le pipeline (et dont le nombre est déterminé par les paramètres donnés à la fonction de dessin).
Les vertex shaders n'ont aucune connaissance de la topologie des primitives rendues. En outre, vous ne pouvez pas vous débarrasser d'un quelconque sommet dans le processeur de sommets. Chaque sommet entre dans le processeur de sommets exactement une fois, subit les transformations et continue dans le pipeline.

L'étape suivante est le « processeur de géométries ».
Dans cette étape, la connaissance de la primitive complète (c'est-à-dire de tous ses sommets) ainsi que des sommets voisins est fournie au shader. Cela permet des techniques qui doivent prendre en compte les informations additionnelles en plus du sommet lui-même.
Le geometry shader a aussi la capacité de changer la topologie de sortie vers une autre que celle choisie lors de l'appel de la fonction de dessin.
Par exemple, vous pourriez lui fournir une liste de points et générer deux triangles (c'est-à-dire un rectangle) à partir de chaque point (technique connue sous le nom de « billboarding »).
De plus, vous avez la possibilité d'émettre de multiples sommets pour chaque appel du geometry shader et ainsi créer de multiples primitives selon la topologie de sortie choisie.

L'étape suivante dans le pipe est le « clipper ».
C'est une unité fixe avec une tâche précise : il découpe les primitives selon la boîte normalisée décrite dans le tutoriel précédent. Il les découpe aussi aux plans Z proche et lointain.
Il est aussi possible de fournir des plans de clipping et faire que le clipper coupe selon ces plans.
Les positions des sommets qui ont survécu au clipper sont maintenant mappées en coordonnées écran et le rasterizer les affiche sur l'écran selon leur topologie.
Par exemple, dans le cas de triangles, ça signifie trouver tous les points qui sont dans le triangle. Pour chaque point, le rasterizer appelle le processeur de fragment. Ici vous avez la possibilité de déterminer la couleur du pixel en la récupérant depuis une texture ou n'importe quelle technique voulue.

Les trois étapes programmables (processeur de sommets, géométrie et fragment) sont optionnelles. Si vous ne leur attachez pas de shader, la fonctionnalité par défaut sera exécutée.

La gestion de shaders est très similaire à la création d'un programme C/C++.
Tout d'abord vous écrivez le texte du shader et le rendez disponible dans votre application. Ça peut être fait simplement en écrivant le texte dans une chaîne de caractères dans le code source même, ou en le chargeant depuis un fichier texte externe (là aussi dans une chaîne de caractères).
Ensuite vous compilez les shaders un par un dans des objets de shader.
Puis vous liez les shaders dans un unique programme et le chargez sur le GPU. Lier les shaders donne au pilote l'opportunité de les optimiser en fonction des relations qu'ils ont entre eux.
Par exemple, vous pourriez coupler un vertex shader qui émet une normale avec un fragment shader qui l'ignore. Dans ce cas le compilateur GLSL du driver peut supprimer les fonctionnalités liées à la normale et permettre une exécution plus rapide du vertex shader. Si plus tard ce shader est couplé avec un fragment shader qui utilise la normale, alors lier l'autre programme générera un vertex shader différent.

Sélectionnez
GLuint ShaderProgram = glCreateProgram();

Nous démarrons le procédé de mise en place de nos shader par la création d'un programme. Nous lierons tous les shaders ensembles à ce programme.

Sélectionnez
GLuint ShaderObj = glCreateShader(ShaderType);

Nous créons deux shaders en utilisant l'appel ci-dessus. L'un d'eux aura pour type de shader GL_VERTEX_SHADER et l'autre aura pour type GL_FRAGMENT_SHADER.

Le procédé pour spécifier la source et compiler le shader est le même pour les deux.

Sélectionnez
const GLchar* p[1];
p[0] = pShaderText;
GLint Lengths[1];
Lengths[0]= strlen(pShaderText);
glShaderSource(ShaderObj, 1, p, Lengths);

Avant la compilation du shader, nous devons spécifier son code source.

La fonction glShaderSource prend le shader comme paramètre et vous laisse une certaine liberté quant à la manière de spécifier la source.

La source peut être définie dans plusieurs chaînes de caractères et vous devez alors fournir un tableau de pointeurs sur ces chaînes ainsi qu'un tableau d'entiers où chaque valeur est la longueur de la chaîne correspondante. Par simplicité, nous utilisons une simple chaîne de caractères pour la source complète et nous utilisons deux tableaux de un élément chacun, pour la source et pour sa longueur.

Le second paramètre de l'appel est le nombre d'emplacements des deux tableaux (juste un dans notre cas).

Sélectionnez
glCompileShader(ShaderObj);

La compilation du shader est très facile…

Sélectionnez
GLint success;
glGetShaderiv(ShaderObj, GL_COMPILE_STATUS, &success);
if (!success)
{
    GLchar InfoLog[1024];
    glGetShaderInfoLog(ShaderObj, sizeof(InfoLog), NULL, InfoLog);
    fprintf(stderr, "Error compiling shader type %d: '%s'\n", ShaderType, InfoLog);
}

… cependant, vous obtiendrez généralement quelques erreurs de compilation. Le bout de code ci-dessus récupère le statut de compilation et affiche toutes les erreurs que le compilateur a rencontrées.

Sélectionnez
glAttachShader(ShaderProgram, ShaderObj);

Finalement nous attachons notre shader au programme. C'est très similaire à la spécification de la liste des objets pour lier dans un makefile. Comme nous n'avons pas de makefile ici, nous émulons ce comportement par programmation. Seuls les shaders attachés prennent part au procédé d'édition de lien.

Sélectionnez
glLinkProgram(ShaderProgram);

Après avoir compilé tous nos shaders et les avoir attachés au programme, nous pouvons finalement le lier.

Notez qu'après la liaison du programme, vous pouvez vous débarrasser des shaders intermédiaires en appelant glDeleteShader pour chacun d'entre eux.

Sélectionnez
glGetProgramiv(ShaderProgram, GL_LINK_STATUS, &Success);
if (Success == 0)
{
    glGetProgramInfoLog(ShaderProgram, sizeof(ErrorLog), NULL, ErrorLog);
    fprintf(stderr, "Error linking shader program: '%s'\n", ErrorLog);
}

Notez que nous vérifions les erreurs en relation avec le programme (comme les erreurs de liaison) d'une manière un peu différente de celles liées aux shaders. Au lieu d'utiliser glGetShaderiv nous utilisons glGetProgramiv et au lieu de glGetShaderInfoLog nous utilisons glGetProgramInfoLog.

Sélectionnez
glValidateProgram(ShaderProgram);

Vous pouvez vous demander pourquoi nous avons besoin de valider le programme après qu'il ait été correctement lié.

La différence est que le l'édition des liens vérifie les erreurs basées sur la combinaison des shaders alors que l'appel ci-dessus vérifie si le programme peut s'exécuter dans l'état actuel du pipeline.

Dans une application complexe avec de multiples shaders et de nombreux changements d'état il est conseillé de valider avant chaque appel de la fonction de dessin. Pour notre application basique, nous ne le vérifions qu'une seule fois. Aussi, vous pouvez vouloir faire cette vérification uniquement pendant le développement et éviter la surcharge dans le produit final.

Sélectionnez
glUseProgram(ShaderProgram);

Finalement, pour utiliser le programme lié, vous l'affectez au pipeline en utilisant l'appel ci-dessus.

Ce programme restera actif pour tous les appels à la fonction de dessin jusqu'à ce que vous le remplaciez par un autre ou que vous le désactiviez explicitement (et activiez le pipeline fixe) en appelant glUseProgram avec NULL.

Si vous avez créé un programme qui contient un seul type de shader alors les autres étapes fonctionnent en utilisant leur fonctionnalité fixe par défaut.

Nous avons terminé l'explication des appels OpenGL relatifs à la gestion de shader.

Le reste de ce tutoriel est relatif au contenu des vertex et fragment shaders (contenus dans les variables pVS et pFS).

Sélectionnez
#version 330

Cette ligne dit au compilateur que nous ciblons la version 3.3 de GLSL. Si le compilateur ne la supporte pas il va émettre une erreur.

Sélectionnez
layout (location = 0) in vec3 Position;

Cette déclaration apparaît dans le vertex shader.

Elle déclare qu'un attribut spécifique au sommet, un vecteur de trois flottants, sera connu sous le nom « Position » dans le shader. « Spécifique au sommet » signifie que pour tous les appels du shader dans le GPU la valeur d'un nouveau sommet venant du tampon sera fournie.

La première section de la déclaration, « layout (location=0) », crée un lien entre le nom de l'attribut et l'attribut dans le tampon. C'est requis pour les cas où notre sommet contient plusieurs attributs (position, normale, coordonnées de texture…). Nous informons le compilateur quel attribut du sommet dans le tampon doit être affecté à l'attribut déclaré dans le shader.

Il y a deux méthodes pour faire cela.

  • Nous pouvons le déclarer explicitement comme nous le faisons ici (à zéro). Dans ce cas nous pouvons utiliser la valeur en dur dans notre application (ce que nous avons fait avec le premier paramètre de l'appel à glVertexAttribPointer).
  • Nous pouvons le laisser indéfini (et simplement déclarer « in vec3 Position » dans le shader) et demander sa position à l'exécution de l'application en utilisant glGetAttribLocation. Dans ce cas nous devrons fournir la valeur retournée à glVertexAttribPointer au lieu de la valeur en dur.

Nous avons choisi la méthode simple mais dans des applications plus complexes il vaut mieux laisser le compilateur déterminer les indices des attributs et les demander à l'exécution. Cela rend plus facile l'intégration de shaders à partir de sources multiples sans avoir à les adapter à la disposition de votre tampon.

Sélectionnez
void main()

Vous pouvez créer votre shader en liant ensemble de multiples shaders. Cependant il n'y a qu'une fonction « main » pour chaque étape de shader (VS, GS ou FS) qui est utilisée comme point d'entrée du shader.

Par exemple vous pouvez créer une bibliothèque de gestion de l'éclairage avec plusieurs fonctions et la lier avec votre shader, si tant est qu'aucune de ses fonctions n'est nommée « main ».

Sélectionnez
gl_Position = vec4(0.5 * Position.x, 0.5 * Position.y, Position.z, 1.0);

Ici nous effectuons des transformations codées en dur sur la position des sommets qui arrivent. Nous divisons par deux les valeurs X et Y et laissons Z inchangé.

gl_Position est une variable prédéfinie spéciale qui est définie pour contenir des coordonnées homogènes (composantes X, Y, Z et W) pour la position du sommet. Le rasterizer va regarder dans cette variable et l'utiliser comme position dans l'espace écran (après quelques transformations supplémentaires). Diviser X et Y par deux signifie que nous allons voir un triangle dont la taille est le quart de celle du triangle du précédent tutoriel.

Notez que nous mettons aussi W à 1.0. C'est extrêmement important afin que le triangle soit affiché correctement.

Effectuer la projection de la 3D vers la 2D est en réalité une action accomplie en deux étapes séparées.

  • Tout d'abord il faut multiplier tous les sommets par la matrice de projection (que nous développerons d'ici quelques tutoriels).
  • Ensuite le GPU effectue ce qui est connu comme le « perspective divide » à l'attribut position avant qu'elle n'atteigne le rasterizer. Cela signifie qu'il divise toutes les composantes de gl_Position par sa composante W.

Dans ce tutoriel, nous ne faisons pas encore de projection dans le vertex shader mais l'étape de « perspective divide » est quelque chose que nous ne pouvons pas désactiver. Quelle que soit la valeur de gl_Position que nous sortons du vertex shader, ses composantes XYZ vont être divisées par le GPU en utilisant sa composante W.

Nous devons nous souvenir que sans celle-ci nous n'aurons pas les résultats que nous attendons. Afin de contourner l'effet de cette division, nous définissons W à 1.0. Une division par 1.0 n'affectera pas les autres composantes de la position du vecteur qui restera alors dans notre boîte normalisée.

Si tout s'est passé correctement, trois sommets avec les valeurs (-0.5, -0.5), (0.5, -0.5) et (0.0, 0.5) atteignent le rasterizer.

Le clipper n'a pas besoin de faire quoi que ce soit, car tous les sommets sont bien dans la boîte normalisée. Ces valeurs sont mappées en coordonnées dans l'espace écran et le rasterizer commence à parcourir tous les points dans le triangle. Pour chaque point le fragment shader est exécuté.

Le code de shader qui suit est pris du fragment shader.

Sélectionnez
out vec4 FragColor;

Habituellement le travail du fragment shader est de déterminer la couleur du fragment (pixel).

En plus, le fragment shader peut complètement se débarrasser du pixel ou changer sa valeur en Z (ce qui affectera le résultat du test de profondeur ultérieur).

La production de la couleur se fait en déclarant la variable ci-dessus. Les quatre composantes représentent R, G, B et A (pour alpha).

La valeur que vous placez dans cette variable sera reçue par le rasterizer et enfin écrite dans le tampon d'image.

Sélectionnez
FragColor = vec4(1.0, 0.0, 0.0, 1.0);

Dans le précédent couple de tutoriels, il n'y avait pas de fragment shader donc tout était dessiné avec la couleur par défaut qui est le blanc. Ici nous définissons FragColor à rouge.

Image non disponible
Résultat

IV. Sources▲

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources

V. Remerciements▲

Merci à Etay Meiri de nous permettre de traduire son tutoriel.

Merci à LittleWhite pour ses corrections et à f-leb pour sa relecture.

Navigation▲

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 ogldev. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -
  • Nous contacter
  • Participez
  • Hébergement
  • Informations légales

Copyright © 2000-2016 - www.developpez.com


Tutoriel 5 - Variables uniformes

Table des matières Plier Déplier

  • II. Contexte
  • III. Explication du code
  • IV. Sources
  • V. Remerciements

Dans ce tutoriel, nous allons rencontrer un nouveau type de variables pour les shaders : les variables uniformes. La différence entre les attributs de sommets et les variables uniformes est que les attributs de sommets contiennent des données qui sont spécifiques aux sommets et sont donc rechargées avec une nouvelle valeur depuis le tampon de sommets à chaque appel du shader.

Au contraire, la valeur des variables uniformes reste constante durant toute la fonction de dessin. Cela signifie que vous chargez la valeur avant d'appeler la fonction de dessin et que vous pouvez accéder à la même valeur à chaque appel du vertex shader.

Les variables uniformes sont utiles pour stocker des données telles que les paramètres de lumière (position, direction…), les matrices de transformation, les identificateurs des objets de texture et ainsi de suite.

Dans ce tutoriel, nous allons enfin avoir quelque chose qui bouge sur l'écran. Nous allons faire cela en utilisant une combinaison de variables uniformes dont nous changerons la valeur à chaque image via le callback d'inactivité fourni par GLUT.

En fait, GLUT n'appelle pas notre callback de rendu de manière répétée, à moins qu'il ne le doive. GLUT doit appeler le callback de rendu à la suite d'événements tels que la minimisation ou maximisation de la fenêtre ou son découvrement par une autre fenêtre.

Si nous ne changeons rien dans l'agencement des fenêtres après le lancement de l'application, le callback de rendu n'est appelé qu'une seule fois. Vous allez voir la sortie une seule fois et vous la verrez ensuite encore si vous minimisez puis maximisez la fenêtre.

Il était suffisant d'enregistrer uniquement le callback de rendu dans GLUT dans les tutoriels précédents mais là, nous souhaitons changer de manière répétée la valeur d'une variable. Nous le faisons en enregistrant un callback d'inactivité. La fonction d'inactivité est appelée par GLUT quand aucun événement venant du système de fenêtrage n'est reçu. Vous pouvez avoir une fonction dédiée pour ce callback, avec laquelle vous pouvez faire n'importe quelle opération de comptage telle que la mise à jour du temps, ou simplement enregistrer votre fonction de rendu en tant que callback d'inactivité.

Dans ce tutoriel nous appliquerons ce dernier choix et mettrons à jour la variable dans la fonction de rendu.

III. Explication du code▲

Sélectionnez
glutIdleFunc(RenderSceneCB);

Ici, nous enregistrons notre fonction de rendu en tant que callback d'inactivité. Notez que si vous choisissez d'utiliser une fonction d'inactivité spécifique, vous devrez appeler glutPostRedisplay à la fin de celle-ci sinon le callback d'inactivité sera appelé encore et encore mais pas la fonction de rendu. glutPostRedisplay marque la fenêtre actuelle comme devant être redessinée et pendant le prochain tour de la boucle principale de GLUT le callback de rendu sera appelé.

Sélectionnez
ScaleLocation = glGetUniformLocation(ShaderProgram, "gScale");
assert(gScaleLocation != 0xFFFFFFFF);

Après avoir lié le programme, nous lui demandons l'emplacement de la variable uniforme.

C'est un autre exemple du cas où l'environnement d'exécution de l'application C/C++ a besoin d'être associé à l'environnement d'exécution du shader. Vous ne pouvez pas accéder directement au contenu du shader et ne pouvez donc pas mettre directement ses variables à jour.

Quand vous compilez le shader, le compilateur GLSL affecte un indice à chacune des variables uniformes. Dans la représentation interne du shader dans le compilateur, l'accès à la variable est possible grâce à cet indice. Cet indice est récupérable par l'application via glGetUniformLocation.

Vous appelez cette fonction avec l'identificateur du programme et le nom de la variable. Cette fonction retourne l'indice ou -1 s'il y a eu une erreur.

Il est très important de vérifier les erreurs (comme nous le faisons ci-dessus avec l'assertion) ou les futures mises à jour de la variable ne seront pas transmises au shader. Il y a principalement deux raisons pour lesquelles cette fonction peut échouer :

  • vous pouvez avoir mal écrit le nom de la variable ;
  • elle a été supprimée suite à une optimisation du compilateur.

Si le compilateur GLSL voit qu'une variable n'est pas utilisée dans le shader, elle est tout simplement supprimée. Dans ce cas glGetUniformLocation échouera.

Sélectionnez
static float Scale = 0.0f;
Scale += 0.001f;
glUniform1f(gScaleLocation, sinf(Scale));

Nous créons une variable flottante statique que nous incrémentons un peu à chaque appel de la fonction de rendu (vous pouvez jouer sur 0.001 si ça va trop lentement ou trop vite sur votre machine).

La valeur qui est réellement passée au shader est le sinus de la variable « Scale », ceci afin de créer une jolie oscillation entre -1.0 et 1.0.

Notez que sinf() prend des radians et non des degrés en paramètre mais à ce stade ça n'a tout simplement pas d'importance. Nous voulons juste la sinusoïde générée par la fonction.

Le résultat de sinf() est passé au shader en utilisant glUniform1f.

OpenGL fournit de multiples instances de cette fonction avec la forme générale glUniform{1234}{if}. Vous pouvez les utiliser pour charger des valeurs dans des vecteurs 1D, 2D, 3D ou 4D (selon le nombre qui suit glUniform) de flottants ou d'entiers (suffixe « f » ou « i »).

Il y a aussi des versions qui prennent l'adresse d'un vecteur en paramètre, ainsi que des versions spéciales pour les matrices.

Le premier paramètre de cette fonction est l'indice de l'emplacement que nous avons récupéré en utilisant glGetUniformLocation.

Nous allons maintenant regarder les changements qui ont été faits dans le vertex shader (le pixel shader reste inchangé).

Sélectionnez
Uniform float gScale;

Ici nous déclarons la variable uniforme dans le shader.

Sélectionnez
gl_Position = vec4(gScale * Position.x, gScale * Position.y, Position.z, 1.0);

Nous multiplions les valeurs X et Y de la position du vecteur avec la valeur qui est changée par l'application à chaque image. Pouvez-vous expliquer pourquoi le triangle est retourné au milieu

de la boucle ?

Image non disponible
Résultat

IV. Sources▲

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources

V. Remerciements▲

Merci à Etay Meiri de nous permettre de traduire son tutoriel.

Merci à LittleWhite pour ses corrections et à Phanloga pour sa relecture.

Navigation▲

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 ogldev. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -
  • Nous contacter
  • Participez
  • Hébergement
  • Informations légales

Copyright © 2000-2016 - www.developpez.com


Tutoriel 06 - Transformations

Translation

Table des matières Plier Déplier

  • II. Contexte
  • III. Explication du code
  • IV. Sources
  • V. Remerciements

Dans ce tutoriel, nous commençons à étudier les diverses transformations que peut subir un objet en 3D, lui permettant d'être affiché sur l'écran tout en maintenant l'impression de profondeur dans la scène.

La méthode commune pour réaliser cela est de représenter chaque transformation en utilisant une matrice, de multiplier les matrices une à une, puis de multiplier la position du sommet par le produit final.

Chacun de ces tutoriels sera dédié à l'étude d'une transformation.

Ici nous étudions la transformation de translation qui est responsable du déplacement d'un objet le long d'un vecteur de taille et de direction quelconques.

Supposons que vous vouliez déplacer le triangle de l'image de gauche à l'emplacement de droite :

Image non disponible

Une façon d'y arriver serait de fournir le vecteur de déplacement (dans notre cas (1, 1)) en tant que variable uniforme au shader et tout simplement de l'ajouter à chaque sommet traité. Cependant cela casse la méthode de multiplication d'un groupe de matrice de manière à obtenir une seule transformation complète.

En outre, nous verrons par la suite que la translation n'est généralement pas la première transformation appliquée, donc il faudra alors multiplier la position par la matrice des transformations antérieures à la translation, puis ajouter la translation et enfin multiplier le résultat par la matrice des transformations postérieures à la translation…

C'est pour le moins alambiqué.

Une meilleure façon de faire est de construire une matrice qui représente la translation et de l'intégrer au processus de multiplication des matrices.

Mais peut-on trouver une matrice qui, multipliée par le point (0, 0) - sommet en bas à gauche du triangle de gauche - donne le point (1, 1) ?

En réalité ce n'est pas possible avec une matrice 2D (ni une matrice 3D, pour le point (0, 0, 0)).

De manière générale, on peut dire qu'il nous faut une matrice M qui selon un point P(x, y, z) et un vecteur V(v1, v2, v3) donnés fournit M * P = P1(x + v1, y + v2, z + v3). Autrement dit, cela signifie que cette matrice M déplace P vers l'emplacement P+V.

Nous voyons que chaque composante de P1 est la somme d'une composante de P et de la composante correspondante de V. La partie gauche de chaque équation de la somme est fournie par la matrice identité :

I * P = P(x, y, z).

Il semble donc que nous devrions commencer par la matrice identité et déterminer les changements qui vont compléter la partie droite de l'équation de la somme de chaque composante (… + V1… + V2… + V3).

Voyons à quoi ressemblerait cette matrice identité améliorée :

Image non disponible

De ce calcul nous pouvons tirer deux conclusions :

  • a, b, c, d, e et f doivent valoir zéro, sinon chaque composante va être influencée par les deux autres (nous sommes donc de retour avec la matrice identité) ;
  • comme x, y et z ont un effet sur les trois composantes du résultat, quand ils valent zéro le résultat vaut le vecteur nul (et nous ne pourrons pas déplacer le vecteur nul)

Nous voulons une matrice qui fournisse la partie droite suivante du calcul :

Image non disponible

Nous devons donc trouver un moyen d'ajouter v1 - v3 comme nous le voyons ci-dessus et a […] f peuvent valoir zéro. Le résultat final sera notre vecteur déplacé.

C'est comme si nous voulions ajouter une quatrième colonne à la matrice mais alors notre calcul serait invalide. Nous ne pouvons pas multiplier une matrice 3 x 4 et un vecteur 3 x 1.

La règle est que l'on ne peut multiplier des matrices que si elles sont de la forme N x M et M x N.

Donc nous devons ajouter une quatrième composante au vecteur. Une bonne quatrième composante sera 1 car nous pouvons mettre v1 - v3 sur la quatrième colonne de la matrice et elles seront inchangées dans le résultat, car multipliées par 1.

Cependant notre matrice est toujours invalide, selon la règle ci-dessus. En effet, multiplier une matrice 3 x 4 par un vecteur 4 x 1 est toujours invalide, ajouter une quatrième ligne à la matrice et la mettre 4 x 4 la rend valide.

Finalement voici notre matrice de translation :

Image non disponible

Maintenant, même si x, y et z valent zéro, nous pouvons toujours les déplacer à n'importe quel emplacement.

La représentation d'un vecteur 3D en vecteur 4D de cette manière, est appelée « coordonnées homogènes » et est très populaire et utile au graphisme 3D. La quatrième composante est appelée « w ».

En fait le symbole interne gl_Position du vertex shader que nous avons vu dans le tutoriel précédent est un vecteur 4D et la composante « w » tient un rôle très important pour transformer la projection 3D en 2D.

La notation commune est d'utiliser w = 1 pour les points et w = 0 pour les vecteurs. La raison est que les points peuvent être déplacés mais pas les vecteurs.

On peut changer la taille d'un vecteur ou sa direction mais tous les vecteurs de même taille et direction sont considérés comme égaux, quelle que soit leur position d'origine. On peut simplement utiliser l'origine du repère pour tous les vecteurs.

Définir w = 0 et multiplier une matrice de translation par le vecteur retournera le même vecteur.

III. Explication du code▲

Sélectionnez
struct Matrix4f
{
    float m[4][4];
};

Nous avons ajouté la définition d'une matrice 4 x 4 au fichier math_3d.h. Elle sera utilisée pour la plupart des matrices de transformation à partir de maintenant.

Sélectionnez
GLuint gWorldLocation;

Nous utilisons cet identificateur pour accéder à la variable uniforme contenant la matrice monde dans le shader. Nous l'appelons « monde », car ce que nous allons faire à l'objet est le déplacer là où nous le voulons, dans le système de coordonnées de notre monde virtuel.

Sélectionnez
Matrix4f World;
World.m[0][0] = 1.0f;
World.m[0][1] = 0.0f;
World.m[0][2] = 0.0f;
World.m[0][3] = sinf(Scale);
World.m[1][0] = 0.0f;
World.m[1][1] = 1.0f;
World.m[1][2] = 0.0f;
World.m[1][3] = 0.0f;
World.m[2][0] = 0.0f;
World.m[2][1] = 0.0f;
World.m[2][2] = 1.0f;
World.m[2][3] = 0.0f;
World.m[3][0] = 0.0f;
World.m[3][1] = 0.0f;
World.m[3][2] = 0.0f;
World.m[3][3] = 1.0f;

Dans la fonction de rendu nous préparons une matrice 4 x 4 et la remplissons en accord avec l'explication ci-dessus. Nous définissons v2 et v3 à zéro pour ne pas avoir de changement sur les composantes Y et Z de l'objet et définissons v1 au résultat du sinus.

Ainsi la coordonnée X sera déplacée d'une valeur qui oscillera entre -1 et 1. Tout ce qu'il nous reste à faire est de charger cette matrice dans le shader.

Sélectionnez
glUniformMatrix4fv(gWorldLocation, 1, GL_TRUE, &World.m[0][0]);

Voici un autre exemple de fonction glUniform* pour charger des données dans les variables uniformes du shader. Cette fonction-ci charge une matrice 4 x 4 et il existe aussi des versions pour les matrices 2 x 2, 2 x 3, 2 x 4, 3 x 2, 3 x 3, 3 x 4, 4 x 2 et 4 x 3.

Le premier paramètre est l'emplacement de la variable uniforme (récupéré après la compilation du shader, via glGetUniformLocation).

Le second paramètre indique le nombre de matrices que nous mettons à jour. Nous y mettons 1 pour une matrice mais nous pouvons aussi utiliser cette fonction pour mettre à jour toutes les matrices de multiplication en un appel.

Le troisième paramètre déroute parfois les nouveaux venus. Il indique si la matrice fournie est exprimée ligne par ligne ou colonne par colonne.

Le fait est que les matrices C/C++ sont exprimées ligne par ligne par défaut. Cela signifie que lorsque l'on remplit un tableau bidimensionnel avec des valeurs, elles sont rangées en mémoire ligne après ligne avec le « haut » à l'adresse la plus basse.

Par exemple, en regardant le tableau suivant :

Sélectionnez
int a[2][3];
a[0][0] = 1;
a[0][1] = 2;
a[0][2] = 3;
a[1][0] = 4;
a[1][1] = 5;
a[1][2] = 6;

Visuellement, le tableau ressemble à la matrice suivante :

1 2 3

4 5 6

Et l'organisation en mémoire ressemble ça : 1 2 3 4 5 6 (avec 1 à l'adresse la plus basse).

Donc notre troisième paramètre de glUniformMatrix4fv est GL_TRUE, car nous fournissons une matrice ligne par ligne. Nous pourrions aussi le mettre à GL_FALSE, mais nous devrions alors transposer notre matrice (l'organisation en mémoire resterait identique, mais OpenGL interpréterait les quatre premières valeurs comme la première colonne et ainsi de suite, et agirait en conséquence).

Le quatrième paramètre est simplement l'adresse de début de la matrice en mémoire.

Le reste de la source provient du code du shader.

Sélectionnez
uniform mat4 gWorld;

Ceci est la variable uniforme d'une matrice 4 x 4. mat2 et mat3 sont aussi disponibles.

Sélectionnez
gl_Position = gWorld * vec4(Position, 1.0);

La position des sommets du triangle dans le tampon de sommets est un vecteur à trois composantes, mais nous avons convenu de la nécessité d'une quatrième composante ayant pour valeur 1.

Nous avons donc deux options :

  • placer des sommets avec quatre composantes dans le tampon de sommets ;
  • ajouter la quatrième composante dans le vertex shader.

Il n'y aucun avantage à utiliser la première option : chaque position de sommet consommera quatre octets supplémentaires en mémoire pour une composante qui vaudra toujours 1.

Il est plus efficace de rester avec un vecteur à trois composantes et concaténer la composante « w » dans le shader. En GLSL cela est fait en utilisant vec4(Position, 1.0).

Nous multiplions la matrice par ce vecteur et stockons le résultat dans gl_Position.

En résumé, à chaque image nous générons une matrice de translation qui déplace la coordonnée X d'une valeur qui oscille entre -1 et 1. Le shader multiplie la position de chaque sommet par cette matrice ce qui se traduit par le déplacement d'un objet de gauche à droite et de droite à gauche.

Dans la plupart de cas, l'un des côtés du triangle sortira de la boîte normalisée après le vertex shader et le clipper le découpera. Nous ne verrons donc que la partie qui se trouve dans la boîte normalisée.

Image non disponible
Résultat

IV. Sources▲

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources

V. Remerciements▲

Merci à Etay Meiri de nous permettre de traduire son tutoriel.

Merci à LittleWhite pour ses corrections et à jacques_jean pour sa relecture.

Navigation▲

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 Ogldev. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -
  • Nous contacter
  • Participez
  • Hébergement
  • Informations légales

Copyright © 2000-2016 - www.developpez.com


Tutoriel 07 - Transformations

Rotation

Table des matières Plier Déplier

  • II. Premier chapitre
  • III. Explication du code
  • IV. Sources
  • V. Remerciements

La prochaine transformation dans notre liste est la rotation, c'est-à-dire que pour un angle et un point donnés, nous voulons tourner le point autour d'un axe. Nous changerons toujours deux des trois coordonnées et laisserons la troisième inchangée. Cela signifie que la transformation se fera sur un des trois plans majeurs : XY (quand on tourne autour de l'axe Z), YZ (quand on tourne autour de l'axe X), XZ (quand on tourne autour de l'axe Y).

Il y a des transformations de rotation plus complexes qui permettent la rotation autour d'un vecteur arbitraire mais nous n'en avons pas encore besoin à cette étape.

Définissons le problème en termes généraux.

Considérons le diagramme suivant :

Image non disponible

Nous voulons nous déplacer sur le cercle du point (x1, y1) au point (x2, y2). Autrement dit, nous voulons tourner le point (x1, y1) selon l'angle α2.

Supposons que le rayon de ce cercle soit 1, ça signifie donc ceci :

Sélectionnez
x1=cos(&#945;1)
y1=sin(&#945;1)
x2=cos(&#945;1+&#945;2)
y2=sin(&#945;1+&#945;2)

Nous allons utiliser les relations trigonométriques suivantes pour développer x2 et y2 :

Sélectionnez
cos(&#945;+b) = cos&#945;.cos&#946; - sin&#945;.sin&#946;
sin(&#945;+b) = sin&#945;.cos&#946; + cos&#945;.sin&#946;

Nous pouvons alors écrire :

Sélectionnez
x2=cos(&#945;1+&#945;2) = cos&#945;1.cos&#945;2 - sin&#945;1.sin&#945;2 = x1.cos&#945;2 - y1.sin&#945;2
y2=sin(&#945;1+&#945;2) = sin&#945;1.cos&#945;2 + cos&#945;1.sin&#945;2 = y1.cos&#945;2 + x1.sin&#945;2

Dans le diagramme ci-dessus, nous regardons le plan XY et Z pointe dans la page. Si X et Y font partie d'un vecteur 4D alors l'équation ci-dessus peut être écrite sous forme matricielle (sans affecter Z ni W) :

Image non disponible

Si nous voulons créer des rotations autour des axes X et Y alors les équations sont globalement les mêmes, mais la matrice est organisée de façon un peu différente. Voici la matrice de rotation autour de l'axe Y :

Image non disponible

Et la matrice de rotation autour de l'axe X :

Image non disponible

III. Explication du code▲

Les changements de code dans ce tutoriel sont mineurs. Nous changeons uniquement le contenu de la matrice de transformation dans le code C/C++.

Sélectionnez
World.m[0][0]=cosf(Scale);
World.m[0][1]=-sinf(Scale);
World.m[0][2]=0.0f;
World.m[0][3]=0.0f;
World.m[1][0]=sinf(Scale);
World.m[1][1]=cosf(Scale);
World.m[1][2]=0.0f;
World.m[1][3]=0.0f;
World.m[2][0]=0.0f;
World.m[2][1]=0.0f;
World.m[2][2]=1.0f;
World.m[2][3]=0.0f;
World.m[3][0]=0.0f;
World.m[3][1]=0.0f;
World.m[3][2]=0.0f;
World.m[3][3]=1.0f;

Comme vous pouvez le voir, nous tournons autour de l'axe Z. Vous pouvez essayer les autres rotations, mais je pense qu'à ce stade, sans une vraie projection de la 3D vers la 2D, les autres rotations vont sembler étranges.

Nous les réaliserons dans une classe de pipeline complet de transformation dans les tutoriels à venir.

Image non disponible
Résultat

IV. Sources▲

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources

V. Remerciements▲

Merci à Etay Meiri de nous permettre de traduire son tutoriel.

Merci à LittleWhite pour ses corrections et à ced pour sa relecture.

Navigation▲

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 Etay Meiri. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -
  • Nous contacter
  • Participez
  • Hébergement
  • Informations légales

Copyright © 2000-2016 - www.developpez.com


Tutoriel 08 - Transformations

Mise à l'échelle

Table des matières Plier Déplier

  • II. Contexte
  • III. Explication du code
  • IV. Sources
  • V. Remerciements

La transformation d'échelle est très simple. Son but est d'augmenter ou réduire la taille de l'objet. Par exemple, vous pouvez vouloir créer une différenciation entre plusieurs objets avec le même modèle (des arbres de taille différente) ou faire correspondre la taille d'un objet avec celle du monde.

Pour les exemples ci-dessus, vous voudrez certainement mettre à l'échelle les positions des sommets pour les trois axes uniformément.

Cependant dans d'autres cas vous voudrez peut-être mettre à l'échelle seulement un ou deux axes, rendant le modèle plus gros ou plus mince.

Le développement de la matrice de transformation est très animé…

Rappelons-nous qu'avec la matrice identité, la raison pour laquelle la multiplication d'un vecteur le laisse inchangé est que chacun des 1 de la diagonale est multiplié par la composante correspondante du vecteur.

Aucune composante ne peut en affecter une autre. Ainsi, remplacer n'importe lequel de ces 1 par une autre valeur impliquera une augmentation sur cet axe, si la valeur est supérieure à 1 ou une diminution sur cet axe sinon.

III. Explication du code▲

Sélectionnez
World.m[0][0]=sinf(Scale);
World.m[0][1]=0.0f;
World.m[0][2]=0.0f;
World.m[0][3]=0.0f;
World.m[1][0]=0.0f;
World.m[1][1]=sinf(Scale);
World.m[1][2]=0.0f;
World.m[1][3]=0.0f;
World.m[2][0]=0.0f;
World.m[2][1]=0.0f;
World.m[2][2]=sinf(Scale);
World.m[2][3]=0.0f;
World.m[3][0]=0.0f;
World.m[3][1]=0.0f;
World.m[3][2]=0.0f;
World.m[3][3]=1.0f;

Le seul changement depuis le tutoriel précédent est que nous remplaçons la matrice monde en concordance avec l'explication ci-dessus.

Comme vous pouvez le voir, nous effectuons la mise à l'échelle sur les trois axes avec une valeur oscillant entre -1 et 1.

Dans l'ensemble de valeurs [0, 1] la taille du triangle est entre « toute petite » et « taille originale », et quand la diagonale vaut zéro il disparaît entièrement.

Entre les bornes [-1, 0], c'est le résultat inverse, car la valeur d'échelle dans la diagonale a changé le signe de la position.

Image non disponible
Résultat

IV. Sources▲

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources

V. Remerciements▲

Merci à Etay Meiri de nous permettre de traduire son tutoriel.

Merci à LittleWhite pour ses corrections et à jacques_jean pour sa relecture.

Navigation▲

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 Etay Meiri. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -
  • Nous contacter
  • Participez
  • Hébergement
  • Informations légales

Copyright © 2000-2016 - www.developpez.com


Tutoriel 9 - Interpolation

Table des matières Plier Déplier

  • II. Contexte
  • III. Explication du code
  • IV. Sources
  • V. Remerciements

Comme vous l'avez déjà vu, afin d'obtenir quelque chose de significatif à l'écran, vous devez utiliser une des variables de sortie du vertex shader : gl_Position. Il s'agit d'un vecteur à quatre dimensions qui contient les coordonnées homogènes du sommet.

Les composantes XYZ de ce vecteur sont divisées par la composante W (procédé connu sous le nom (anglais) de « perspective divide » et que nous expliquons dans le tutoriel dédié à ce sujet) et toutes les composantes qui sortent de la boîte normalisée ([-1, 1] pour XY ; [0, 1] pour Z) sont découpées.

Le résultat est transformé dans le système de coordonnées écran et ensuite le triangle (ou toute autre type de primitive supporté) est rendu à l'écran par le rasterizer.

Le rasterizer effectue une interpolation entre les trois sommets du triangle (soit de ligne en ligne ou par une autre technique) et « visite » chaque pixel dans le triangle en exécutant le fragment shader.

Le fragment shader doit renvoyer une couleur de pixel que le rasterizer place dans le tampon de couleur pour l'affichage (après plusieurs étapes additionnelles telles que le test de profondeur, etc.).

N'importe quelle autre variable sortant du vertex shader ne passe pas par les étapes décrites ci-dessus.

Si le fragment shader ne requiert pas explicitement cette variable (et vous pouvez mixer et associer plusieurs fragment shaders avec le même vertex shader), alors une optimisation commune sera de supprimer toutes les instructions, dans le vertex shader, qui n'affectent que cette variable (pour ce programme spécifique qui combine cette paire de vertex shader et fragment shader).

Cependant si le fragment shader utilise cette variable, le rasterizer l'interpole durant la rasterization et fournit à chaque invocation du fragment shader la valeur interpolée qui correspond à cet emplacement spécifique.

Cela signifie généralement que les valeurs pour les pixels qui sont proches les uns des autres seront un peu différentes (même si, le triangle s'éloignant de plus en plus de la caméra, cela devient moins probable).

Deux variables très communes se basent sur l'interpolation : la normale du triangle et les coordonnées de texture.

La normale d'un sommet est habituellement calculée comme la moyenne des normales de tous les triangles qui contiennent ce sommet. Si cet objet n'est pas complètement plat, cela signifie habituellement que les normales des trois sommets de chaque triangle seront différentes les unes des autres

Dans ce cas, nous nous fions à l'interpolation pour calculer la normale de chaque pixel. Cette normale est utilisée dans les calculs d'éclairage afin de générer une représentation plus crédible des effets de lumières.

Le cas des coordonnées de texture est similaire. Ces coordonnées font partie du modèle et sont spécifiées par sommet. Afin de couvrir le triangle avec une texture, vous devez effectuer l'opération d'échantillonnage pour chaque pixel et spécifier les coordonnées de texture correctes pour ce pixel. Ces coordonnées sont le résultat de l'interpolation.

Dans ce tutoriel nous allons voir les effets de l'interpolation en interpolant différentes couleurs au travers de la face triangulaire.

Comme je suis fainéant, je vais générer la couleur dans le vertex shader.

Une approche plus fastidieuse serait de la fournir dans le tampon de sommets. Généralement nous ne fournissons pas de couleurs à partir du tampon de sommets, nous fournissons plutôt des coordonnées de texture et échantillonnons la couleur à partir de la texture.

Cette couleur sera ensuite utilisée par les calculs d'éclairage.

III. Explication du code▲

Les paramètres passés entre les étapes du pipeline doivent être déclarés en utilisant le mot clé out dans le contexte global du shader.

La couleur est un vecteur à quatre dimensions car les composantes XYZ contiennent les composantes RGB (respectivement) et la composante W correspond à la valeur d'alpha (transparence du pixel).

Sélectionnez
Color = vec4(clamp(Position, 0.0, 1.0), 1.0);

La couleur, dans les pipelines graphiques, est généralement représentée en utilisant des valeurs en nombres flottants dans l'ensemble [0.0, 1.0].

Cette valeur est ensuite mise en correspondance avec un entier entre 0 et 255 pour chaque canal de la couleur (pour un total de 16 M de couleurs).

Nous définissons la couleur du sommet comme fonction de sa position. Tout d'abord nous utilisons la fonction intégrée clamp() afin d'être sûr que les valeurs ne sortent pas des bornes [0.0, 1.0].

La raison est que le sommet en bas à gauche du triangle est positionné en (-1, -1). Si nous prenons cette valeur telle quelle, elle sera interpolée par le rasterizer et, jusqu'à ce que X et Y dépassent zéro, nous ne verrons rien car chaque valeur inférieure ou égale à zéro sera rendue en noir.

Cela signifie que la moitié du bord dans chaque direction sera noire avant que la couleur dépasse zéro et devienne significative. En fixant la valeur, nous faisons en sorte que seule la partie en bas à gauche soit noire, mais comme nous nous en éloignons rapidement, la couleur devient rapidement plus claire.

Essayez de jouer avec la fonction clamp(), en l'enlevant ou en modifiant ses paramètres, pour en voir l'effet.

Le résultat de la fonction clamp() ne va pas directement dans la variable de sortie, car c'est un vecteur à quatre dimensions, alors que la position est un vecteur à trois dimensions (clamp() ne change pas le nombre de composantes, seulement leur valeur.).

Du point de vue de GLSL il n'y a pas de conversion par défaut pour cela et nous devons donc le faire explicitement.

Nous le faisons en utilisant la notation vec4(vec3, W) qui crée un vecteur 4D en concaténant un vecteur 3D avec la valeur W fournie. Dans notre cas nous utilisons 1.0, car elle s'inscrit dans la composante alpha de la couleur et nous voulons que le pixel soit complètement opaque.

Sélectionnez
in vec4 Color;

La partie opposée de la couleur sortie du vertex shader est la couleur en entrée du fragment shader. Cette variable subit l'interpolation par le rasterizer afin que chaque fragment shader soit (probablement) exécuté avec une couleur différente.

Sélectionnez
FragColor = Color;

Nous utilisons la couleur interpolée comme couleur du fragment sans autre changement et cela termine ce tutoriel.

Image non disponible
Résultat

IV. Sources▲

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources

V. Remerciements▲

Merci à Etay Meiri de nous permettre de traduire son tutoriel.

Merci à LittleWhite pour ses corrections et à jacques_jean pour sa relecture.

Navigation▲

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 Etay Meiri. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -
  • Nous contacter
  • Participez
  • Hébergement
  • Informations légales

Copyright © 2000-2016 - www.developpez.com


Tutoriel 10 - Dessins indexés

Table des matières Plier Déplier

  • II. Contexte
  • III. Explication du code
  • IV. Sources
  • V. Remerciements

OpenGL fournit diverses fonctions de dessins. glDrawArrays, que nous avons utilisé jusqu'à présent, est classée dans la catégorie des « dessins ordonnés ». Cela signifie que le tampon de sommets est parcouru à partir de l'indice spécifié et, pour chaque X sommets (1 pour les points, 2 pour les lignes…), une primitive est générée. C'est très simple d'utilisation, cependant le revers de la pièce est que, si un sommet est utilisé dans plusieurs primitives, alors il doit être présent plusieurs fois dans le tampon de sommets. C'est-à-dire qu'il n'y a pas de concept de partage de sommets.

Le partage de sommets est fourni par les fonctions de dessins appartenant à la catégorie des « dessins indexés ». Là, en plus du tampon de sommets, il y a aussi un tampon d'indices qui contient les indices du tampon de sommets. Le parcours du tampon d'indices est similaire au parcours du tampon de sommets : tous les X indices, une primitive est générée. Pour vous exercer au partage de sommets, vous pouvez simplement répéter l'indice du sommet partagé plusieurs fois. Le partage de sommets est très important en termes d'occupation mémoire, car la plupart des objets sont représentés par des maillages fermés de triangles, et la plupart des sommets prennent part dans la composition de plus d'un triangle.

Voici un exemple de dessin ordonné :

Image non disponible

Si nous effectuons un rendu de triangles, le GPU va générer la liste suivante : V0/1/2, V3/4/5 V6/7/8, etc.

Voici un exemple de dessin indexé :

Image non disponible

Dans ce cas le GPU va générer les triangles suivants : V4/0/1, V5/2/1, V6/1/7, etc.

L'utilisation de dessins indexés en OpenGL requiert la génération et le remplissage d'un tampon d'indices. Ce tampon doit être lié, en plus du tampon de sommets, avant la fonction de dessin et une fonction différente doit être utilisée.

III. Explication du code▲

Sélectionnez
GLuint IBO;

Nous avons ajouté un nouvel identifiant pour le tampon d'indices.

Sélectionnez
Vertices[0] = Vector3f(-1.0f, -1.0f, 0.0f);
Vertices[1] = Vector3f(0.0f, -1.0f, 1.0f);
Vertices[2] = Vector3f(1.0f, -1.0f, 0.0f);
Vertices[3] = Vector3f(0.0f, 1.0f, 0.0f);

Pour montrer le partage de sommets, nous avons besoin d'un maillage un peu plus complexe. De nombreux tutoriels utilisent le fameux cube tournant pour cela. Il requiert huit sommets et douze triangles. Comme je suis fainéant, j'utilise la pyramide tournante à la place. Elle requiert uniquement quatre sommets et quatre triangles, ce qui est beaucoup plus rapide à générer manuellement.

Lorsque l'on regarde ces sommets de dessus (le long de l'axe Y), nous voyons le schéma suivant :

Image non disponible
Sélectionnez
unsigned int Indices[] = { 0, 3, 1,
                           1, 3, 2,
                           2, 3, 0,
                           0, 1, 2 };

Le tampon d'indices est rempli en utilisant un tableau d'indices. Les indices correspondent à la position des sommets dans le tampon d'indices. Lorsque l'on regarde le tableau et le schéma au-dessus, nous pouvons voir que le dernier triangle est la base de la pyramide, tandis que les trois autres constituent ses faces. La pyramide n'est pas symétrique mais est très facile à spécifier.

Sélectionnez
glGenBuffers(1, &IBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices), Indices, GL_STATIC_DRAW);

Nous créons et remplissons le tampon d'indices en utilisant notre tableau. Vous pouvez voir que la seule différence, entre la création d'un tampon de sommets et la création d'un tampon d'indices, est que le tampon de sommets prend GL_ARRAY_BUFFER en tant que type de tampon, alors que le tampon d'indices prend GL_ELEMENT_ARRAY_BUFFER.

Sélectionnez
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);

En plus de lier le tampon de sommets, nous devons aussi lier le tampon d'indices avant de dessiner. Là encore, nous utilisons GL_ELEMENT_ARRAY_BUFFER comme type de tampon.

Sélectionnez
glDrawElements(GL_TRIANGLES, 12, GL_UNSIGNED_INT, 0);

Nous utilisons glDrawElements au lieu de glDrawArrays. Le premier paramètre est le type de primitives à dessiner (comme pour glDrawArrays). Le second paramètre est le nombre d'indices, parmi ceux contenus dans le tampon d'indices, à utiliser pour la génération des primitives. Le paramètre suivant est le type de chaque indice. Le GPU doit être informé de la taille de chaque indice, sinon il ne saura pas comment parcourir le tampon. Les valeurs possibles ici sont GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT, GL_UNSIGNED_INT. Si l'intervalle d'indices est petit, nous voudrons le plus petit type de données possible, qui prend moins de place en mémoire, et si l'intervalle d'indices est grand, nous voudrons les types les plus larges. Le dernier paramètre indique au GPU le point de départ en octets à partir duquel il commence le parcours du tampon. C'est utile quand le même tampon d'indices contient les indices de plusieurs objets. En spécifiant un point de départ et un nombre d'indices, nous précisons au GPU quel objet dessiner. Dans notre cas nous voulons commencer au début, donc nous spécifions zéro. Notons que le type du dernier paramètre est Glvoid*, ainsi, si nous voulons spécifier autre chose que zéro, nous devrons le convertir dans ce type.

IV. Sources▲

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources

V. Remerciements▲

Merci à Etay Meiri de nous permettre de traduire son tutoriel.

Merci à LittleWhite pour ses corrections et à jacques_jean pour sa relecture.

Navigation▲

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 Etay Meiri. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -
  • Nous contacter
  • Participez
  • Hébergement
  • Informations légales

Copyright © 2000-2016 - www.developpez.com


Tutoriel 11 - Concaténation de transformations

Table des matières Plier Déplier

  • II. Contexte
  • III. Explication du code
  • IV. Sources
  • V. Remerciements

Dans les derniers tutoriels, nous avions mis en place diverses transformations nous donnant la possibilité de déplacer un objet n'importe où dans un monde en 3D.

Nous en avons encore quelques-unes à apprendre (contrôle de caméra et projection en perspective), mais comme vous l'avez certainement deviné, une combinaison des transformations est nécessaire. Dans la plupart des cas, vous voudrez mettre un objet à l'échelle afin qu'il s'intègre correctement à votre monde 3D, le tourner dans l'orientation voulue, le déplacer quelque part, etc.

Jusqu'à présent, nous nous sommes exercés sur une seule transformation à la fois. Afin de mettre en place la série de transformations, nous devons multiplier la première matrice de transformation par la position du sommet, puis multiplier la matrice suivante par le résultat de cette multiplication, et ainsi de suite jusqu'à ce que toutes les matrices de transformation soient appliquées au sommet.

Une manière triviale de faire cela est de fournir toutes les matrices de transformation au shader et de le laisser faire les multiplications. Cependant, c'est très inefficace, car les matrices sont les mêmes pour tous les sommets et seule la position du sommet change. Par chance, l'algèbre linéaire fournit un ensemble de règles qui vont nous rendre la vie plus facile. Il est dit que pour un ensemble de matrices M0…Mn et un vecteur V, l'équation suivante est vraie :

Sélectionnez
Mn * Mn-1 * ... * M0 * V = (Mn* Mn-1 * ... * M0) * V

Donc, si on calcule :

Sélectionnez
N = Mn * Mn-1 * ... * M0

alors :

Sélectionnez
Mn * Mn-1 * ... * M0 * V = N * V

Cela signifie que nous pouvons calculer N une fois, puis l'envoyer au shader via une variable uniforme où elle sera multipliée à chaque sommet. Cela requiert donc une seule multiplication matrice/vecteur par sommet au niveau du GPU.

Comment ordonner les matrices lors de la génération de N ? La première chose dont il faut se souvenir est que le vecteur est d'abord multiplié par la matrice la plus à droite de la série (dans notre cas M0). Puis, le vecteur est transformé par chaque matrice de droite à gauche. En graphisme 3D, vous voudrez généralement mettre l'objet à l'échelle, puis l'orienter, le déplacer, lui appliquer la transformation de caméra et enfin le projeter en 2D. Voyons ce qui arrive lorsque vous tournez avant de déplacer :

Image non disponible

Voyons maintenant ce qui arrive lorsque vous déplacez avant de tourner :

Image non disponible

Comme vous pouvez le voir, il est très difficile de définir la position de l'objet dans le monde s'il est d'abord déplacé, car si vous le déplacez de son point d'origine, puis que vous le tournez, il tourne autour de l'origine, ce qui signifie que vous le déplacez encore. Le second déplacement est une chose que vous voulez éviter. En le tournant avant de le déplacer, vous retirez la connexion entre les deux opérations. C'est pour cela qu'il est toujours mieux de modéliser un objet autour de l'origine de la manière la plus symétrique possible. De cette manière, lorsque plus tard vous mettrez à l'échelle ou tournerez votre objet, il n'y aura pas d'effet de bord et l'objet mis à l'échelle ou tourné restera aussi symétrique qu'auparavant.

Maintenant que nous commençons à manipuler plusieurs transformations, nous devons perdre l'habitude de mettre à jour la matrice directement dans la fonction de rendu. Cette méthode n'est pas évolutive et est source d'erreur. À la place, une classe de pipeline est introduite. Cette classe masque les détails des manipulations de matrice au travers d'une interface simple afin de changer le déplacement, la rotation, etc. Après avoir défini tous les paramètres, vous extrayez simplement la matrice combinant toutes les transformations. Cette matrice peut alors être directement donnée au shader.

III. Explication du code▲

Sélectionnez
#define ToRadian(x) ((x) * M_PI / 180.0f)
#define ToDegree(x) ((x) * 180.0f / M_PI)

Nous commençons à utiliser les valeurs réelles des angles dans ce tutoriel. Les fonctions de la bibliothèque standard du C prennent des radians en paramètres. Les macros ci-dessus prennent un angle soit en radians, soit en degrés et le convertissent dans l'autre notation.

Sélectionnez
inline Matrix4f operator*(const Matrix4f& Right) const
{
    Matrix4f Ret;
    for (unsigned int i = 0 ; i < 4 ; i++) {
        for (unsigned int j = 0 ; j < 4 ; j++) {
            Ret.m[i][j] = m[i][0] * Right.m[0][j] +
                m[i][1] * Right.m[1][j] +
                m[i][2] * Right.m[2][j] +
                m[i][3] * Right.m[3][j];
        }
    }

    return Ret;
}

Cet opérateur prend en charge la multiplication de matrices. Comme vous pouvez le voir, chaque valeur de la matrice résultat est définie comme le produit scalaire de sa ligne dans la matrice de gauche avec sa colonne de la matrice de droite. Cet opérateur est la clef de l'implémentation de la classe du pipeline.

Sélectionnez
class Pipeline
{
public:
    Pipeline()
    { ...  }

    void Scale(float ScaleX, float ScaleY, float ScaleZ)
    { ... }

    void WorldPos(float x, float y, float z)
    { ... }

    void Rotate(float RotateX, float RotateY, float RotateZ)
    { ... }

    const Matrix4f* GetTrans();
private:
    Vector3f m_scale;
    Vector3f m_worldPos;
    Vector3f m_rotateInfo;
    Matrix4f m_transformation;
};

Le pipeline cache les détails de la récupération de la combinaison de toutes les transformations requises pour un simple objet. Il y a actuellement trois vecteurs membres privés qui contiennent la mise à l'échelle, la position dans l'espace monde et la rotation sur chaque axe. Il y a en plus les interfaces pour les définir et une fonction pour récupérer la matrice représentant la somme de ces transformations.

Sélectionnez
const Matrix4f* Pipeline::GetTrans()
{
        Matrix4f ScaleTrans, RotateTrans, TranslationTrans;
        InitScaleTransform(ScaleTrans);
        InitRotateTransform(RotateTrans);
        InitTranslationTransform(TranslationTrans);
        m_transformation = TranslationTrans * RotateTrans * ScaleTrans;
        return &m_transformation;
}

Cette fonction initialise trois matrices différentes, une par transformation, contenant la configuration actuelle. Elle les multiplie une par une et retourne le produit final. Notez que l'ordre est codé en dur et suit la description ci-dessus. Si vous avez besoin de plus de flexibilité ici, vous pouvez utiliser un masque de bits qui spécifiera l'ordre. Notez aussi que la transformation finale est stockée en tant que membre. Vous pouvez essayer d'optimiser cette fonction en vérifiant un indicateur de modification et en retournant la matrice stockée s'il n'y a pas eu de modifications dans la configuration depuis la dernière fois que cette fonction a été appelée.

Cette fonction utilise des méthodes privées pour générer les différentes transformations à partir de ce que nous avons appris lors des précédents tutoriels. Dans les tutoriels suivants, cette classe sera étendue pour gérer les contrôles de caméra et la perspective de projection.

Sélectionnez
Pipeline p;
p.Scale(sinf(Scale * 0.1f), sinf(Scale * 0.1f), sinf(Scale * 0.1f));
p.WorldPos(sinf(Scale), 0.0f, 0.0f);
p.Rotate(sinf(Scale) * 90.0f, sinf(Scale) * 90.0f, sinf(Scale) * 90.0f);
glUniformMatrix4fv(gWorldLocation, 1, GL_TRUE, (const Glfloat*)p.GetTrans());

Voici les changements dans la fonction de rendu. Nous allouons un objet de pipeline, le configurons et envoyons la transformation résultante au shader. Vous pouvez jouer avec les paramètres pour voir leur effet sur l'image finale.

IV. Sources▲

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources

V. Remerciements▲

Merci à Etay Meiri qui nous a permis de traduire son tutoriel.

Merci à LittleWhite pour ses corrections et à milkoseck pour sa relecture orthographique.

Navigation▲

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 Etay Meiri. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -
  • Nous contacter
  • Participez
  • Hébergement
  • Informations légales

Copyright © 2000-2016 - www.developpez.com


Tutoriel 12 - Projection en perspective

Table des matières Plier Déplier

  • II. Contexte
  • III. Explication du code
  • IV. Sources
  • V. Remerciements

Dans ce tutoriel, vous allez découvrir comment projeter vos objets 3D sur une fenêtre 2D.

Nous avons enfin atteint l'élément qui représente le mieux les graphismes 3D : la projection du monde 3D au monde 2D tout en maintenant l'impression de profondeur. Un bon exemple est l'image d'une route ou d'un rail de chemin de fer qui converge vers un point distant à l'horizon.

Nous allons générer la transformation qui satisfait les contraintes ci-dessus et une contrainte supplémentaire : nous allons rendre la vie plus facile au clipper en représentant les coordonnées projetées dans l'espace normalisé de -1 à +1. Cela signifie que le clipper peut faire son travail sans avoir connaissance ni des dimensions de l'écran, ni de la position des plans Z (plan Z proche et plan Z lointain).

La transformation de projection en perspective va nous imposer de fournir quatre paramètres :

  1. Le ratio d'aspect : le ratio entre la largeur et la hauteur de la zone rectangulaire qui sera la cible de la projection ;
  2. Le champ vertical : l'angle vertical de la caméra au travers duquel nous regardons le monde ;
  3. La position du plan Z proche qui nous permet de découper les objets trop proches de la caméra ;
  4. La position du plan Z lointain qui nous permet de découper les objets trop loin de la caméra.

Le ratio d'aspect est requis, car nous allons représenter toutes les coordonnées dans un espace normalisé dans lequel la hauteur est égale à la largeur. Comme c'est rarement le cas avec un écran où la largeur est généralement plus grande que la hauteur, il faut les représenter dans la transformation en « condensant » les points sur la ligne horizontale par rapport à la ligne verticale. Cela nous permettra d'intégrer plus de coordonnées en termes de composante X dans l'espace normalisé, ce qui satisfera la contrainte de « voir » plus en largeur qu'en hauteur sur l'image finale.

Le champ vertical nous permet de zoomer en avant et en arrière sur le monde. Considérons l'exemple suivant. Dans l'image de gauche, l'angle est plus grand, ce qui rend les objets plus petits alors que sur l'image de droite, l'angle est plus petit et fait paraître le même objet plus grand. Notez que cela a un effet sur la position de la caméra, ce qui est un peu contre intuitif. Sur l'image de droite (où nous zoomons avec un plus petit champ), la caméra doit être placée plus loin, et sur l'image de gauche, elle est plus proche du plan de projection. Cependant, il faut savoir que cela n'a pas d'effet réel, car les coordonnées projetées sont mappées sur l'écran et la position de la caméra n'a aucun effet dans ce calcul.

Image non disponible

Nous commençons par déterminer la distance du plan de projection par rapport à la caméra. Le plan de projection est parallèle au plan XY. Évidemment, le plan entier n'est pas visible, car il est trop grand. Nous voyons uniquement les objets dans une zone rectangulaire (appelée fenêtre de projection) qui a les mêmes proportions que notre écran. Le ratio d'aspect est calculé comme suit :

Sélectionnez
ar = largeur écran / hauteur écran
Image non disponible

Pour plus de simplicité, nous définissons la hauteur de la fenêtre de projection à 2, ce qui signifie que la largeur vaut exactement deux fois le ratio d'aspect (selon l'équation ci-dessus). Si nous plaçons la caméra à l'origine et regardons la zone de derrière la caméra, nous voyons ceci :

Tout ce qui se trouve au-dehors de ce rectangle sera découpé et nous voyons déjà que les points à l'intérieur de ce rectangle auront leurs coordonnées dans l'intervalle voulu. La composante X est pour l'instant un peu plus grande, mais nous allons fournir une correction plus tard.

Maintenant, jetons un œil là-dessus, vu de côté (en regardant le plan YZ) :

Image non disponible
Image non disponible

Nous calculons la distance entre la caméra et le plan de projection en utilisant le champ vertical (représenté par l'angle alpha) :

L'étape suivante est de calculer les coordonnées projetées pour X et Y. Considérons l'image suivante (là aussi en regardant le plan YZ) :

Image non disponible

Nous avons un point dans le monde 3D avec les coordonnées (x, y, z). Nous voulons trouver (xp, yp) qui représente les coordonnées projetées sur le plan de projection. Comme la composante X n'est pas dans la portée de ce diagramme (il pointe hors de la page), nous allons commencer par Y. À partir du théorème de Thalès, nous pouvons déterminer l'équation suivante :

Image non disponible

De la même manière, pour la composante X :

Image non disponible

Comme les dimensions de notre fenêtre de projection sont 2 * ar (largeur) par 2 (hauteur), nous savons qu'un point du monde 3D dans la fenêtre est projeté sur un point dont la composante X projetée est entre -ar et +ar et la composante Y projetée entre -1 et +1. Donc, la composante Y est normalisée, mais pas la composante X. Nous pouvons normaliser la composante Xp en la divisant par le ratio d'aspect. Cela signifie qu'un point dont la composante X projetée valait +ar vaut maintenant +1, ce qui la place tout à droite de la boîte normalisée. Si sa composante X projetée valait +0,5 et le ratio valait 1,333 (ce que l'on obtient lorsque nous avons une résolution de 1024 x 768), la nouvelle coordonnée X projetée vaut 0,375. En résumé, la division par le ratio d'aspect a pour effet de condenser les points sur l'axe X.

Nous avons atteint les équations suivantes pour la projection des composantes X et Y.

Image non disponible

Avant d'aller plus loin, essayons de voir à quoi ressemble la matrice de projection à ce stade. Cela signifie que nous allons représenter les équations ci-dessus en utilisant une matrice. Nous rencontrons maintenant un problème. Dans les deux équations, nous avons besoin de diviser X, Y et Z qui font partie du vecteur position. Cependant, la valeur de Z change d'un sommet à l'autre, donc nous ne pouvons pas le placer dans une seule matrice afin de projeter tous les sommets. Pour mieux comprendre cela, prenons le premier vecteur ligne de la matrice (a, b, c, d). Nous avons besoin de choisir les valeurs de ce vecteur de manière à ce que l'équation suivante se vérifie :

Image non disponible

C'est le produit scalaire entre le premier vecteur ligne de la matrice avec le vecteur position qui calcule la composante X finale. Nous pouvons définir « b » et « d », mais nous ne pouvons pas trouver de « a » ou de « c » qui puisse correspondre. La solution adoptée par OpenGL est de séparer la transformation en deux parties : la multiplication par la matrice de projection suivie de la division par Z en étape indépendante. La matrice fournie par l'application et le shader doivent inclure la multiplication de la position par Z. La division par la composante Z est effectuée en dur au niveau du GPU et prend effet dans le rasterizer (quelque part entre le vertex shader et le fragment shader). Comment le GPU connaît-il la sortie du vertex shader qu'il doit diviser par la composante Z ? Très simplement grâce à la variable gl_Position qui est là pour cela. Maintenant, nous devons juste trouver une matrice qui représente les équations de projection de X et Y ci-dessus.

Après la multiplication par cette matrice, le GPU peut diviser par Z automatiquement pour nous et nous récupérons le résultat que nous voulons. Mais il y a une autre complexité : si nous multiplions la matrice par la position du sommet, puis le divisons par Z, nous perdons complètement la valeur Z, car elle devient égale à 1 pour tous les sommets. La valeur Z originale doit être sauvegardée afin de pouvoir effectuer le test de profondeur par la suite. L'astuce est de copier la valeur Z originale dans la composante W du vecteur résultat, et de diviser seulement X, Y et Z par W au lieu de Z. W garde alors la valeur originale de Z qui peut être utilisée pour le test de profondeur. L'étape automatique de division de gl_Position par sa composante W est appelée « perspective divide ».

Image non disponible

Nous pouvons maintenant générer une matrice intermédiaire qui représente nos deux équations ainsi que la copie de Z dans la composante W :

Comme je l'ai dit plus tôt, nous voulons aussi inclure la normalisation de la valeur Z, car c'est plus facile pour le clipper de travailler sans connaître les plans Z proche et lointain. Cependant, la matrice ci-dessus met Z à zéro. Sachant qu'après la transformation du vecteur, le système va automatiquement faire l'étape de « perspective divide », nous avons besoin de définir les valeurs de la troisième ligne de la matrice afin que la division par Z dans l'intervalle de vue (c'est-à-dire : NearZ <= Z <= FarZ) mappe ces valeurs dans l'intervalle [-1, 1]. Cette opération de mappage est décomposée en deux parties. Tout d'abord, nous réduisons l'intervalle [NearZ, FarZ] à un intervalle de largeur 2. Puis, nous déplaçons cet intervalle de manière à ce qu'il débute à -1. La mise à l'échelle de la valeur Z, puis son déplacement sont représentés par la fonction générale :

Image non disponible

Mais après l'étape de « perspective divide », la partie droite de la fonction devient :

Image non disponible

Maintenant, nous devons trouver les valeurs de A et de B qui vont effectuer le mappage vers [-1, 1]. Nous savons que si Z vaut NearZ, le résultat doit valoir -1, et si Z vaut FarZ, le résultat doit valoir 1. Donc nous pouvons écrire :

Image non disponible

Maintenant, nous devons définir la troisième ligne de la matrice et le vecteur (a, b, c, d) qui satisfait l'équation suivante :

Image non disponible

Nous définissons tout de suite « a » et « b » à zéro, car nous ne voulons pas que X et Y aient un effet quelconque sur la transformation de Z. Donc, notre valeur A peut devenir « c » et notre valeur B peut devenir « d » (car W vaut 1).

Image non disponible

Ainsi, la matrice de transformation finale est :

Après la multiplication du vecteur position par la matrice projection, les coordonnées sont dites dans le « Clip Space ». Après l'étape de « perspective divide », les coordonnées sont dans le « NDC Space » (Normalized Device Coordinates).

Le chemin que nous avons suivi dans cette série de tutoriels devrait maintenant être clair. Sans faire de projection, nous pouvons simplement sortir du vertex shader des sommets dont les composantes XYZ (du vecteur position) sont dans l'intervalle [-1, 1]. Cela permet d'être certain qu'ils finissent quelque part sur l'écran. En s'assurant que W vaut toujours 1, nous nous prémunissons contre le fait que l'étape de « perspective divide » ait un effet. Après que les coordonnées soient transformées en coordonnées écran, nous aurons fini. En utilisant la matrice de projection, l'étape de « perspective divide » devient partie intégrante du processus de projection de 3D vers 2D.

III. Explication du code▲

Sélectionnez
void Pipeline::InitPerspectiveProj(Matrix4f& m) const
{
    const float ar = m_persProj.Width / m_persProj.Height;
    const float zNear = m_persProj.zNear;
    const float zFar = m_persProj.zFar;
    const float zRange = zNear - zFar;
    const float tanHalfFOV = tanf(ToRadian(m_persProj.FOV / 2.0));

    m.m[0][0] = 1.0f / (tanHalfFOV * ar);
    m.m[0][1] = 0.0f;
    m.m[0][2] = 0.0f;
    m.m[0][3] = 0.0f;

    m.m[1][0] = 0.0f;
    m.m[1][1] = 1.0f / tanHalfFOV;
    m.m[1][2] = 0.0f;
    m.m[1][3] = 0.0f;

    m.m[2][0] = 0.0f;
    m.m[2][1] = 0.0f;
    m.m[2][2] = (-zNear - zFar) / zRange;
    m.m[2][3] = 2.0f * zFar * zNear / zRange;

    m.m[3][0] = 0.0f;
    m.m[3][1] = 0.0f;
    m.m[3][2] = 1.0f;
    m.m[3][3] = 0.0f;
}

Une structure appelée m_persProj a été ajoutée à la classe Pipeline. Cette structure contient la configuration de la projection en perspective. La méthode ci-dessus génère la matrice que nous avons développée dans la section « Contexte ».

Sélectionnez
m_transformation = PersProjTrans * TranslationTrans * RotateTrans * ScaleTrans;

Nous ajoutons la matrice de projection en tant que première opérande de la multiplication qui génère la transformation complète. Souvenez-vous que comme le vecteur position est multiplié de droite à gauche, cette matrice est donc en fait la dernière. D'abord, nous mettons à l'échelle, puis nous tournons, déplaçons et enfin projetons.

Sélectionnez
p.SetPerspectiveProj(30.0f, WINDOW_WIDTH, WINDOW_HEIGHT, 1.0f, 1000.0f);

Dans la fonction de rendu, nous définissons les paramètres de la projection. Vous pouvez jouer avec ceux-ci afin de constater leur effet.

IV. Sources▲

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources

V. Remerciements▲

Merci à Etay Meiri qui nous a permis de traduire son tutoriel.

Merci à LittleWhite pour ses corrections et à milkoseck pour sa relecture orthographique.

Navigation▲

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 Etay Meiri. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -
  • Nous contacter
  • Participez
  • Hébergement
  • Informations légales

Copyright © 2000-2016 - www.developpez.com


Tutoriel 13 - Espace caméra

Table des matières Plier Déplier

  • I. Contexte
  • II. Explication du code
  • III. Sources
  • IV. Remerciements

Dans ce tutoriel, nous allons voir comment placer une caméra n'importe où dans le monde 3D.

Dans les derniers tutoriels, nous avons vu deux types de transformations. Les transformations du premier type sont celles qui changent la position (déplacement), l'orientation (rotation) ou la taille (mise à l'échelle) d'un objet. Ces transformations nous permettent de positionner un objet n'importe où dans un monde en 3D. Les transformations du second type sont les transformations de projection en perspective, qui prennent la position d'un sommet dans le monde en 3D et le projettent dans un monde en 2D (c'est-à-dire : un plan). Une fois que les coordonnées sont en 2D, il est très facile de les mapper directement en coordonnées de l'espace écran. Ces coordonnées sont utilisées pour transformer les primitives dont l'objet est composé (que ce soit des points, lignes ou triangles).

La pièce manquante au puzzle est la position de la caméra. Dans tous les tutoriels précédents, nous avons implicitement considéré que la caméra était simplement positionnée à l'origine de notre espace 3D. En réalité nous voulons avoir la liberté de placer la caméra n'importe où dans le monde et projeter les sommets sur un plan 2D en face d'elle. Cela reflétera la relation réelle entre la caméra et un objet sur l'écran.

Dans l'image suivante nous voyons la caméra positionnée quelque part, nous tournant le dos. On trouve aussi un plan 2D virtuel devant elle et une balle est projetée sur le plan. La caméra est inclinée et le plan suit cette inclinaison. Comme la vue de la caméra est limitée par son angle de vue, la partie visible du plan 2D (infini) est le rectangle. Tout ce qui se trouve hors de ce rectangle sera découpé. Notre objectif est de rendre le rectangle sur l'écran.

Image non disponible

Théoriquement, il est possible de générer les transformations qui projetteraient un objet du monde 3D sur un plan 2D face à une caméra placée à une position arbitraire du monde. Cependant les mathématiques associées seraient bien plus complexes que ce que nous avons vu précédemment. Il est beaucoup plus simple de faire cela lorsque la caméra est placée à l'origine du monde 3D, regardant le long de l'axe Z. Par exemple, un objet est placé en (0, 0, 5) et la caméra est en (0, 0, 1), regardant le long de l'axe Z (donc directement vers l'objet). Si nous déplaçons en même temps la caméra et l'objet d'une unité vers l'origine, alors la distance et l'orientation relatives (en termes de direction de la caméra) restent les mêmes, mais la caméra est alors placée à l'origine. Déplacer tous les objets de la scène de cette manière va nous permettre de rendre la scène correctement en utilisant les méthodes préalablement apprises.

L'exemple ci-dessus était simple car la caméra regardait déjà le long de l'axe X et était alignée sur les axes du système de coordonnées. Mais qu'arrive-t-il lorsque la caméra regarde ailleurs ? Regardons l'image suivante. Par simplicité, c'est un système de coordonnées à deux dimensions et nous regardons la caméra du dessus.

Image non disponible

À l'origine, la caméra regardait le long de l'axe X mais elle a tourné de 45° dans le sens des aiguilles d'une montre. Comme vous pouvez le voir, la caméra définit son propre système de coordonnées qui peut être identique à celui du monde (image du haut) ou être différente (image du bas). Il y a donc deux systèmes de coordonnées simultanément. Il y a le « système de coordonnées monde » dans lequel les objets sont spécifiés, et le système de coordonnées de la caméra qui est aligné sur les axes de la caméra (cible, haut et droite). Ces deux systèmes de coordonnées sont connus sous le nom de « espace monde » et « espace caméra/vue ».

La balle verte est située en (0, y, z) dans l'espace monde. Dans l'espace caméra elle est située quelque part dans le quart en haut à gauche du système de coordonnées (c'est-à-dire qu'elle a un X négatif et un Z positif). Nous devons déterminer la position de la balle dans l'espace caméra. Nous pourrons alors oublier l'espace monde et utiliser uniquement l'espace caméra. Dans l'espace caméra, la caméra est placée à l'origine et regarde le long de l'axe Z. Les objets sont exprimés de façon relative à la caméra et peuvent être rendus en utilisant les outils que nous avons mis en place.

Dire que la caméra a tourné de 45° dans le sens des aiguilles d'une montre est la même chose que dire que la balle verte a tourné de 45° dans le sens inverse des aiguilles d'une montre. Le mouvement des objets est toujours l'opposé du mouvement de la caméra. Donc plus généralement, nous devons ajouter deux nouvelles transformations et les brancher au pipeline de transformation que nous avions jusqu'à maintenant. Nous devons déplacer les objets de telle manière qu'ils maintiennent leur distance à la caméra pendant son déplacement à l'origine, et nous devons les tourner dans la direction opposée à la direction que la caméra prend.

Déplacer la caméra est très simple si elle est située en (x, y, z), alors la transformation de déplacement est (-x, -y, -z). La raison est directe : la caméra était placée dans le monde en utilisant une transformation de déplacement basée sur le vecteur (x, y, z), donc pour la replacer à l'origine nous devons utiliser la transformation de déplacement opposée. C'est pourquoi la matrice de transformation ressemble à ça :

Image non disponible

L'étape suivante est de tourner la caméra vers une cible dont les coordonnées sont exprimées dans l'espace monde. Nous voulons trouver la position des sommets dans le nouveau système de coordonnées défini par la caméra. La vraie question est donc : comment transformer des coordonnées d'un système à un autre ?

Regardons une fois encore l'image ci-dessus. Nous pouvons dire que le système de coordonnées monde est défini par trois vecteurs linéairement indépendants (1, 0, 0), (0, 1, 0) et (0, 0, 1). Linéairement indépendants signifie qu'il n'existe pas de coordonnées x, y et z différentes de zéro telles que x(1, 0, 0) + y(0, 1, 0) + z(0, 0, 1) = (0, 0, 0). En termes géométriques, cela signifie que chaque paire de vecteurs parmi ces trois, définit un plan qui est orthogonal au troisième vecteur (le plan XY est orthogonal à l'axe Z, etc.). Il est facile de voir que le système de coordonnées de la caméra est défini par les vecteurs (1, 0, -1), (0, 1, 0) et (1, 0, 1). Après normalisation de ces vecteurs, on obtient (0.7071, 0, -0.7071), (0, 1, 0) et (0.7071, 0, 0.7071).

L'image suivante montre comment la position d'un vecteur est exprimée dans deux systèmes de coordonnées différents :

Image non disponible

Nous savons comment récupérer les vecteurs unitaires qui représentent les axes de la caméra dans l'espace monde et nous connaissons la position du vecteur dans l'espace monde (x, y, z). Nous recherchons le vecteur (x', y', z'). Nous allons maintenant profiter d'une propriété du produit scalaire connue comme « projection orthogonale ». La projection orthogonale est le résultat du produit scalaire entre un vecteur quelconque A et un vecteur unitaire B, dont le résultat est la longueur de A dans la direction de B. En d'autres termes, la projection du vecteur A sur le vecteur B. Dans l'exemple ci-dessus, si nous faisons un produit scalaire entre (x, y, z) et le vecteur unitaire qui représente l'axe X de la caméra, nous obtenons x'. En procédant de la même manière nous pouvons obtenir y' et z'.(x', y', z') est la position de (x, y, z) dans l'espace caméra.

Voyons comment organiser cette connaissance en solution complète pour orienter la caméra. La solution est appelée « UVN camera » et est un système parmi d'autres permettant d'exprimer l'orientation d'une caméra. L'idée est que la caméra est définie par trois vecteurs.

  1. N : le vecteur de la caméra vers sa cible. Il est aussi connu sous le nom de « look at » dans certaines documentations 3D. Ce vecteur correspond à l'axe Z.
  2. V : pour une personne debout, il s'agit du vecteur pointant de la tête vers le ciel. Si vous écrivez un simulateur de vol et que l'avion est à l'envers, ce vecteur peut aussi ponter vers le sol. Ce vecteur correspond à l'axe Y.
  3. U : Ce vecteur pointe de la caméra vers son côté « droit ». Il correspond à l'axe X.

Afin de transformer une position de l'espace monde vers l'espace caméra défini par les vecteurs UVN, nous devons effectuer un produit scalaire entre la position et les vecteurs UVN. Une matrice représente mieux cela :

Image non disponible

Dans le code qui accompagne ce tutoriel, vous remarquerez que la variable globale de shader « gWorld » a été renommée « gWVP ». Ce changement reflète la façon par laquelle la série de transformations est connue dans de nombreux livres. WVP signifie « World-View-Projection ».

II. Explication du code▲

Dans ce tutoriel j'ai décidé de modifier un peu la conception et ai déplacé le code bas niveau de manipulation des matrices de la classe Pipeline vers la classe Matrix4f. La classe Pipeline demande maintenant à la classe Matrix4f de s'initialiser elle-même de diverses manières et concatène les multiples matrices pour créer la transformation finale.

pipeline.h
Sélectionnez
85.
86.
87.
88.
89.
struct {
    Vector3f Pos;
    Vector3f Target;
    Vector3f Up;
} m_camera;

La classe Pipeline a quelques membres pour stocker les paramètres de la caméra. Notez que l'axe qui pointe de la caméra vers sa droite est manquant (l'axe U). Il est calculé à la volée en utilisant un produit vectoriel entre les axes cible et haut. De plus il y a une nouvelle fonction nommée SetCamera pour définir ces valeurs.

Math3d.h
Sélectionnez
21.
22.
23.
24.
25.
26.
27.
28.
Vector3f Vector3f::Cross(const Vector3f& v) const
{
    const float _x = y * v.z - z * v.y;
    const float _y = z * v.x - x * v.z;
    const float _z = x * v.y - y * v.x;

    return Vector3f(_x, _y, _z);
}

La classe Vector3f possède une nouvelle méthode permettant de calculer le produit vectoriel entre deux Vector3f. Un produit vectoriel entre deux vecteurs, produit un vecteur qui est orthogonal au plan défini par les vecteurs. Cela devient plus intuitif lorsque l'on se souvient que les vecteurs ont une direction et une longueur mais pas de position. Tous les vecteurs ayant les mêmes directions et longueurs sont considérés comme égaux, quelque soit l'emplacement où ils « commencent ». Donc vous pourriez aussi bien faire que ces deux vecteurs commencent à l'origine. Cela signifie que vous pouvez créer un triangle qui a un sommet à l'origine et deux sommets au bout des vecteurs. Ce triangle définit un plan et le produit vectoriel est un vecteur qui est orthogonal à ce plan.

Math3d.h
Sélectionnez
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
Vector3f& Vector3f::Normalize()
{
    const float Length = sqrtf(x * x + y * y + z * z);

    x /= Length;
    y /= Length;
    z /= Length;

    return *this;
}

Pour générer la matrice UVN nous allons devoir mettre ces vecteurs à l'unité. Cette opération est connue comme « normalisation de vecteur » et est exécutée en divisant chaque composante du vecteur par la longueur du vecteur.

Math3d.h
Sélectionnez
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
void Matrix4f::InitCameraTransform(const Vector3f& Target, const Vector3f& Up)
{
    Vector3f N = Target;
    N.Normalize();
    Vector3f U = Up;
    U.Normalize();
    U = U.Cross(Target);
    Vector3f V = N.Cross(U);

    m[0][0] = U.x; m[0][1] = U.y; m[0][2] = U.z; m[0][3] = 0.0f;
    m[1][0] = V.x; m[1][1] = V.y; m[1][2] = V.z; m[1][3] = 0.0f;
    m[2][0] = N.x; m[2][1] = N.y; m[2][2] = N.z; m[2][3] = 0.0f;
    m[3][0] = 0.0f; m[3][1] = 0.0f; m[3][2] = 0.0f; m[3][3] = 1.0f;
}

Cette fonction génère la matrice de transformation de caméra, qui sera utilisée plus tard par la classe Pipeline. Les vecteurs U, V et N sont calculés et mis dans la matrice en tant que lignes. Comme la position du sommet va être multipliée en tant qu'opérande droit (et donc en tant que vecteur colonne), cela représente un produit scalaire entre U, V et N et la position. Cela génère les trois projections orthogonales qui deviennent les valeurs XYZ de la position en espace caméra.

La fonction est renseignée avec les vecteurs « cible » et « haut ». Le vecteur « droite » est calculé comme le produit vectoriel entre eux. Notez que nous ne faisons pas confiance à l'appelant pour le passage de vecteurs unitaires, donc nous les normalisons de toute façon. Après avoir généré le vecteur U, nous recalculons le vecteur « haut » comme le produit vectoriel entre le vecteur « cible » et le vecteur « droite ». La raison deviendra plus claire dans le futur, lorsque nous commencerons à déplacer la caméra. Il est plus simple de mettre à jour uniquement le vecteur « cible » et de laisser le vecteur « haut » non modifié. Cependant cela signifie que l'angle entre le vecteur « cible » et le vecteur « haut » ne sera pas de 90°, ce qui nous générera un système de coordonnées invalide. En recalculant le vecteur « droite » avec un produit vectoriel entre les vecteurs « cible » et « haut », puis en recalculant le vecteur « haut » avec un produit vectoriel entre les vecteurs « cible » et « droite », nous obtenons un système de coordonnées avec des angles droits entre chaque paire d'axes (un système orthonormé).

Pipeline.cpp
Sélectionnez
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
const Matrix4f* Pipeline::GetTrans()
{
    Matrix4f ScaleTrans, RotateTrans, TranslationTrans, CameraTranslationTrans, CameraRotateTrans, PersProjTrans;

    ScaleTrans.InitScaleTransform(m_scale.x, m_scale.y, m_scale.z);
    RotateTrans.InitRotateTransform(m_rotateInfo.x, m_rotateInfo.y, m_rotateInfo.z);
    TranslationTrans.InitTranslationTransform(m_worldPos.x, m_worldPos.y, m_worldPos.z);
    CameraTranslationTrans.InitTranslationTransform(-m_camera.Pos.x, -m_camera.Pos.y, -m_camera.Pos.z);
    CameraRotateTrans.InitCameraTransform(m_camera.Target, m_camera.Up);
    PersProjTrans.InitPersProjTransform(m_persProj.FOV, m_persProj.Width, m_persProj.Height, m_persProj.zNear, m_persProj.zFar);

    m_transformation = PersProjTrans * CameraRotateTrans * CameraTranslationTrans * TranslationTrans * RotateTrans * ScaleTrans;
    return &m_transformation;
}

Mettons à jour la fonction qui génère la matrice de transformation complète pour un objet. Elle devient assez complexe avec deux nouvelles matrices qui fournissent la partie caméra. Après le calcul de la transformation monde (la combinaison du déplacement, de la rotation et de la mise à l'échelle de l'objet), nous commençons la transformation de la caméra, en « déplaçant » la caméra à l'origine. Cela est fait par le déplacement en utilisant le vecteur négatif de la position de la caméra. Donc si la caméra est positionnée en (1, 2, 3) nous devons déplacer l'objet de (-1, -2, -3) afin d'avoir la caméra à l'origine. Après cela, nous générons la matrice de rotation de la caméra basée sur les vecteurs « cible » et « haut » de la caméra. Cela termine la partie caméra. Enfin, nous projetons les coordonnées.

Tutorial13,cpp
Sélectionnez
76.
77.
78.
79.
Vector3f CameraPos(1.0f, 1.0f, -3.0f);
Vector3f CameraTarget(0.45f, 0.0f, 1.0f);
Vector3f CameraUp(0.0f, 1.0f, 0.0f);
p.SetCamera(CameraPos, CameraTarget, CameraUp);

Nous utilisons la nouvelle fonctionnalité dans la boucle de rendu. Pour placer la caméra nous reculons par rapport à l'origine sur l'axe Z négatif, puis nous la déplaçons sur la droite et vers le haut. Le vecteur « haut » est tout simplement l'axe Y positif. Nous définissons tout cela dans l'objet Pipeline, la classe Pipeline s'occupe du reste.

III. Sources▲

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources

IV. Remerciements▲

Merci à Etay Meiri qui nous a permis de traduire son tutoriel.

Merci à LittleWhite pour ses corrections et à jacques_jean pour sa relecture orthographique.

Navigation▲

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+ inPartager

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 Etay Meiri. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -
  • Nous contacter
  • Participez
  • Hébergement
  • Informations légales

Copyright © 2000-2016 - www.developpez.com


Tutoriel 14 - Contrôle de la caméra

Partie 1

Table des matières Plier Déplier

  • I. Contexte
  • II. Explication du code
  • III. Sources
  • IV. Remerciements

Ce tutoriel initie au contrôle de la caméra par l'utilisateur. Il se focalise sur le contrôle au clavier.

Dans le tutoriel précédent, nous avions vu comment placer la caméra n'importe où dans le monde 3D. L'étape suivante est logiquement de permettre à l'utilisateur de la contrôler. Les mouvements ne seront pas restreints : l'utilisateur sera capable de bouger dans toutes les directions. Le contrôle de la caméra sera effectué en utilisant deux périphériques d'entrée : le clavier pour contrôler la position et la souris pour changer notre point de vue. C'est similaire à ce que la plupart des « First Person Shooter » font. Ce tutoriel va se concentrer sur le clavier et le suivant se focalisera sur la souris.

Nous allons gérer les quatre flèches directionnelles de manière conventionnelle. Rappelez-vous que notre transformation de caméra est définie par la position, par le vecteur cible et par le vecteur haut. Quand nous nous déplaçons en utilisant le clavier, nous changeons uniquement la position. Nous ne pouvons pas incliner la caméra, ni la tourner ; donc les vecteurs cible et haut restent inchangés.

Pour contrôler le clavier, nous utilisons une autre fonction de la bibliothèque GLUT : glutSpecialFunc(). Cette fonction enregistre un callback qui est appelé quand une touche « spéciale » est appuyée. Les touches spéciales comprennent : les touches F1 à F12, les flèches directionnelles, Page Haut, Page Bas, Origine, Fin et Insert. Si vous voulez récupérer les touches classiques (caractères et numériques), utilisez glutKeyboardFunc().

II. Explication du code▲

Les fonctionnalités de la caméra sont encapsulées dans la classe Camera. Cette classe regroupe les propriétés de la caméra et peut les modifier en fonction des événements de mouvement qu'elle reçoit. Les propriétés sont récupérées par la classe Pipeline qui génère la matrice de transformation basée dessus.

Camera.h
Sélectionnez
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
class Camera
{
public:

 Camera();
 Camera(const Vector3f& Pos, const Vector3f& Target, const Vector3f& Up);
 bool OnKeyboard(int Key);
    const Vector3f& GetPos() const
    const Vector3f& GetTarget() const
    const Vector3f& GetUp() const

private:

    Vector3f m_pos;
    Vector3f m_target;
    Vector3f m_up;
};

Ceci est la déclaration de la classe Camera. Elle stocke les trois propriétés définissant la caméra : les vecteurs position, cible et haut. Deux constructeurs sont disponibles. Celui par défaut place simplement la caméra à l'origine, regardant le long de l'axe Z positif, avec un vecteur haut pointant vers le ciel (0, 1, 0). Il est aussi possible de créer la caméra avec des propriétés spécifiques. La fonction OnKeyboard() transmet les événements clavier à la caméra. Elle retourne un booléen qui indique si l'événement a été consommé par la classe. Si la touche est appropriée (une des flèches directionnelles), la valeur de retour est true, sinon false. De cette manière, vous pouvez construire une chaîne de clients qui reçoivent les événements clavier et vous arrêter après le premier qui fait réellement quelque chose de cet événement.

Camera.cpp
Sélectionnez
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
bool Camera::OnKeyboard(int Key)
{
    bool Ret = false;

    switch (Key) {

    case GLUT_KEY_UP:
    {
        m_pos += (m_target * StepSize);
        Ret = true;
    }
    break;

    case GLUT_KEY_DOWN:
    {
        m_pos -= (m_target * StepSize);
        Ret = true;
    }
    break;

    case GLUT_KEY_LEFT:
    {
        Vector3f Left = m_target.Cross(m_up);
        Left.Normalize();
        Left *= StepSize;
        m_pos += Left;
        Ret = true;
    }
    break;

    case GLUT_KEY_RIGHT:
    {
        Vector3f Right = m_up.Cross(m_target);
        Right.Normalize();
        Right *= StepSize;
        m_pos += Right;
        Ret = true;
    }
    break;
    }

    return Ret;
}

Cette fonction déplace la caméra en fonction des événements clavier. GLUT définit des macros correspondant aux flèches directionnelles, c'est ce sur quoi l'instruction « switch » est basée. Malheureusement ces macros sont de simples « int » au lieu d'une énumération.

Les mouvements avant et arrière sont les plus simples. Comme le mouvement est toujours le long du vecteur cible, nous avons seulement besoin d'ajouter ou de soustraire celui-ci de la position. Le vecteur cible lui-même reste inchangé. Notez qu'avant d'ajouter ou de soustraire le vecteur cible, nous le multiplions par une valeur constante appelée StepSize. Nous faisons cela pour chaque flèche directionnelle. StepSize fournit un point unique pour modifier la vitesse de déplacement (plus tard nous envisagerons de la transformer en propriété de classe). Pour rendre StepSize constante, nous devons nous assurer de toujours la multiplier à des vecteurs unitaires (c'est-à-dire que nous devons nous assurer que les vecteurs cible et haut sont toujours unitaires).

Le déplacement sur le côté est un peu plus complexe. Il est défini comme un mouvement le long d'un vecteur orthogonal au plan créé par les vecteurs cible et haut. Ce plan divise l'espace 3D en deux parties. Il y a deux vecteurs qui y sont orthogonaux et ils sont opposés l'un à l'autre. Nous pouvons appeler l'un « gauche » et l'autre « droite ». Ils sont générés en utilisant un produit vectoriel des vecteurs cible et haut dans les deux combinaisons possibles : cible fois haut ou haut fois cible (le produit vectoriel n'est pas une opération commutative : changer l'ordre des paramètres génère un résultat différent). Après avoir récupéré le vecteur gauche/droite, nous le normalisons, le multiplions par StepSize et l'ajoutons au vecteur position (ce qui le déplace vers la gauche ou la droite). Là aussi, les vecteurs cible et haut sont inchangés.

Notez que les opérations de cette fonction utilisent quelques nouveaux opérateurs tels que += et -= qui ont été ajoutés à la classe Vector3f.

tutorial14.cpp
Sélectionnez
73.
74.
75.
76.
77.
78.
static void InitializeGlutCallbacks()
{
    glutDisplayFunc(RenderSceneCB);
    glutIdleFunc(RenderSceneCB);
    glutSpecialFunc(SpecialKeyboardCB);
}

Ici, nous enregistrons un nouveau callback afin de traiter les événements clavier spéciaux. Le callback reçoit la touche et la position de la souris au moment où la touche est appuyée. Nous ignorons la position de la souris et donnons l'événement à une instance de la classe caméra allouée dans la section globale du fichier.

Tutorial14.cpp
Sélectionnez
55.
p.SetCamera(GameCamera.GetPos(), GameCamera.GetTarget(), GameCamera.GetUp());

Précédemment, nous avions initialisé les paramètres de la caméra dans la classe Pipeline en utilisant des vecteurs codés en dur. Maintenant nous avons supprimé ces vecteurs et nous utilisons les propriétés de la caméra directement à partir de la classe Camera.

III. Sources▲

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources

IV. Remerciements▲

Merci à Etay Meiri de nous permettre de traduire son tutoriel.

Merci à LittleWhite pour ses corrections et à Malick SECK pour sa relecture.

Navigation▲

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+ inPartager

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 Etay Meiri. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -
  • Nous contacter
  • Participez
  • Hébergement
  • Informations légales

Copyright © 2000-2016 - www.developpez.com


Tutoriel 15 - Contrôle de la caméra

Partie 2

Table des matières Plier Déplier

  • I. Contexte
  • II. Explication du code
  • III. Sources
  • IV. Remerciements

Ce tutoriel poursuit l'implémentation du contrôle de la caméra par l'utilisateur. Il se focalise sur le contrôle à la souris. Il introduit aussi la notion de quaternion.

Dans ce tutoriel, nous allons terminer l'implémentation de la caméra en activant le contrôle de la direction par la souris. Il y a divers degrés de liberté associés à la conception de caméra. Nous allons permettre le degré de contrôle que l'on pourrait attendre dans un jeu à la première personne. Cela signifie que vous pourrez tourner la caméra à 360° (autour de l'axe Y), ce qui correspondrait à tourner la tête ou faire un tour complet avec le corps. Nous serons de plus capables d'incliner la caméra en haut et en bas pour pouvoir regarder au-dessus ou en dessous. Nous ne serons pas capables d'incliner la caméra jusqu'à faire un tour complet, ou de l'incliner dans l'autre sens (en vrille). Ces degrés de liberté sont du domaine des simulateurs de vol, qui sont hors de la portée de ce tutoriel. Néanmoins, nous aurons une caméra nous permettant d'explorer confortablement le monde en 3D que nous développerons dans les tutoriels suivants.

Le canon sol-air de la Seconde Guerre mondiale suivant montre le type de caméra que nous allons construire :

Image non disponible

Ce canon possède deux axes de contrôle :

  1. Il peut tourner à 360 degrés autour de l'axe (0, 1, 0). Cet angle est appelé « angle horizontal » et le vecteur est « l'axe vertical » ;
  2. Il peut s'incliner vers le haut et vers le bas autour d'un vecteur parallèle au sol. Ce mouvement est limité et le canon ne peut pas effectuer de cercle complet. Cet angle est appelé « angle vertical » et le vecteur est « l'axe horizontal ». Notez qu'alors que l'axe vertical est constant (0, 1, 0), l'axe horizontal quant à lui tourne avec le canon et est toujours perpendiculaire à la cible du canon. C'est un point-clef à intégrer pour comprendre correctement les formules à venir.

L'objectif est de suivre le mouvement de la souris, de changer l'angle horizontal quand la souris se déplace à gauche et à droite, et l'angle vertical quand la souris se déplace en haut et en bas. Étant donné ces deux angles, nous voulons calculer le vecteur cible et le vecteur haut.

Tourner le vecteur cible selon l'angle horizontal est évident. En utilisant de la trigonométrie basique, nous pouvons voir que la composante Z du vecteur cible est le sinus de l'angle horizontal tandis que la composante X en est le cosinus (à ce stade la caméra regarde droit devant donc Y vaut zéro. Retournez au septième tutoriel pour avoir une image de cela.

Tourner le vecteur cible selon l'angle vertical est plus complexe, car l'axe horizontal tourne avec la caméra. L'axe horizontal peut être calculé en faisant un produit vectoriel entre l'axe vertical et le vecteur cible après qu'il a été tourné selon l'angle horizontal, mais tourner autour d'un vecteur quelconque (pour lever ou baisser le canon) peut être ardu.

Heureusement nous avons un outil mathématique extrêmement utile pour ce problème : le quaternion. Les quaternions ont été découverts en 1843 par Sir William Rovan Hamilton, un mathématicien irlandais, et sont basés sur l'ensemble des nombres complexes. Le quaternion « Q » est défini comme suit :

Image non disponible

où i, j et k sont des nombres complexes et l'équation suivante est vraie :

Image non disponible

En pratique, nous définissons un quaternion comme un vecteur 4D (x, y, z, w). Le conjugué du quaternion « Q » est défini comme suit :

Image non disponible

La normalisation d'un quaternion est la même opération que pour un vecteur. Je vais décrire les étapes requises afin de tourner un vecteur autour d'un autre vecteur quelconque, en utilisant un quaternion. Plus de détails sur la preuve mathématique à l'origine de ces étapes peuvent être trouvés sur internet.

La fonction générale utilisée pour calculer un quaternion « W » représentant le vecteur « V » tourné selon un angle « a » est :

Image non disponible

Où Q est le quaternion de rotation défini comme suit :

Image non disponible

Après avoir calculé « W », le vecteur orienté est simplement (W.x, W.y, W.z). Un point important à noter dans le calcul de « W » est que nous devons d'abord multiplier « Q » par « V », ce qui est une multiplication quaternion par vecteur qui a pour résultat un quaternion, puis nous devons faire une multiplication quaternion par quaternion (le résultat de Q*V multiplié par le conjugué de Q). Les deux types de multiplications ne sont pas les mêmes. Le fichier math3d.cpp intègre les implémentations de ces types de multiplications.

Nous devons maintenir les angles horizontal et vertical à jour, car l'utilisateur déplace la souris dans l'écran et nous devons décider de comment les initialiser. Le choix logique est de les initialiser en fonction du vecteur cible fourni au constructeur de la caméra. Commençons par l'angle horizontal. Regardons l'image suivante, qui regarde le plan XZ vu de dessus :

Image non disponible

Le vecteur cible est (x, z) et nous voulons calculer l'angle horizontal qui est représenté par la lettre alpha (la composante Y n'étant utile que pour l'angle vertical). Comme la longueur du cercle vaut 1, il est assez simple de voir que le sinus d'alpha est exactement z. Donc calculer l'arc sinus de z donnera alpha. En avons-nous terminé ? Pas encore. Comme z peut être dans l'intervalle [-1, 1], le résultat de l'arc sinus va de -90° à +90°. Cependant l'angle horizontal peut valoir jusque 360°. De plus, notre quaternion tourne dans le sens des aiguilles d'une montre. Cela signifie que lorsque nous tournons de 90° avec le quaternion, nous finissons à -1 sur l'axe Z, ce qui est l'opposé du sinus de 90° (qui vaut 1). À mes yeux, la manière la plus simple d'obtenir le bon résultat est d'utiliser l'arc sinus avec la valeur absolue de Z et de combiner le résultat avec le quartier du cercle dans lequel le vecteur se trouve. Par exemple, quand notre vecteur cible est (0, 1), nous calculons l'arc sinus de 1 qui est donc 90 et le retranchons de 360. Le résultat est 270. L'intervalle d'arc sinus pour des valeurs entre 0 et 1 est de 0 à 90°. En combinant cela avec le quartier spécifique du cercle, nous obtenons l'angle horizontal final.

Calculer l'angle vertical est un peu plus simple. Nous allons limiter l'intervalle de rotation de -90° (égal à 270° : regarder verticalement en haut) à +90° (regarder verticalement vers le bas). Cela signifie que nous avons juste besoin de la valeur négative de l'arc sinus de la composante Y dans le vecteur cible. Quand Y vaut 1 (regarder en haut) l'arc sinus vaut 90, nous avons juste à inverser le signe. Quand Y vaut -1 (regarder en bas), l'arc sinus vaut alors -90 et inverser son signe fait donc 90. Si vous êtes troublé, regardez une nouvelle fois l'image en remplaçant simplement Z par Y et X par Z.

II. Explication du code▲

Camera.cpp
Sélectionnez
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
Camera::Camera(int WindowWidth, int WindowHeight, const Vector3f& Pos, const Vector3f& Target, const Vector3f& Up)
{
    m_windowWidth = WindowWidth;
    m_windowHeight = WindowHeight;
    m_pos = Pos;

    m_target = Target;
    m_target.Normalize();

    m_up = Up;
    m_up.Normalize();

    Init();
}

Le constructeur de la caméra récupère maintenant les dimensions de la fenêtre. Nous en avons besoin afin de déplacer la souris au centre de l'écran. De plus, notez l'appel à la méthode Init() qui définit les propriétés internes à la caméra.

camera.cpp
Sélectionnez
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
void Camera::Init()
{
    Vector3f HTarget(m_target.x, 0.0, m_target.z);
    HTarget.Normalize();

    if (HTarget.z >= 0.0f)
    {
        if (HTarget.x >= 0.0f)
        {
            m_AngleH = 360.0f - ToDegree(asin(HTarget.z));
        }
        else
        {
            m_AngleH = 180.0f + ToDegree(asin(HTarget.z));
        }
    }
    else
    {
        if (HTarget.x >= 0.0f)
        {
            m_AngleH = ToDegree(asin(-HTarget.z));
        }
        else
        {
            m_AngleH = 90.0f + ToDegree(asin(-HTarget.z));
        }
    }

    m_AngleV = -ToDegree(asin(m_target.y));

    m_OnUpperEdge = false;
    m_OnLowerEdge = false;
    m_OnLeftEdge = false;
    m_OnRightEdge = false;
    m_mousePos.x = m_windowWidth / 2;
    m_mousePos.y = m_windowHeight / 2;

    glutWarpPointer(m_mousePos.x, m_mousePos.y);
}

Dans la fonction Init(), nous commençons par calculer l'angle horizontal. Nous créons un nouveau vecteur cible appelé HTarget (cible horizontale) qui est une projection du vecteur cible original sur le plan XZ. Puis nous le normalisons (car les formules décrites plus tôt utilisent un vecteur unitaire sur le plan XZ). Enfin nous vérifions à quel quartier du cercle le vecteur cible appartient et calculons l'angle final, basé sur la valeur positive de la composante Z. Ensuite nous calculons l'angle vertical, ce qui est beaucoup plus simple.

La caméra a quatre nouveaux indicateurs pour indiquer si la souris est placée sur un des bords de l'écran. Nous allons implémenter une rotation automatique dans la direction correspondante lorsque cela arrive. Cela nous permettra de tourner à 360°. Nous initialisons ces indicateurs à FALSE, car la souris commence au milieu de l'écran. Les deux lignes suivantes calculent où se trouve le centre de l'écran (basé sur les dimensions de la fenêtre), et la fonction glutWarpPointer() déplace effectivement la souris. Commencer avec la souris au centre de l'écran nous rend la vie beaucoup plus simple.

camera.cpp
Sélectionnez
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
void Camera::OnMouse(int x, int y)
{
    const int DeltaX = x - m_mousePos.x;
    const int DeltaY = y - m_mousePos.y;

    m_mousePos.x = x;
    m_mousePos.y = y;

    m_AngleH += (float)DeltaX / 20.0f;
    m_AngleV += (float)DeltaY / 20.0f;

    if (DeltaX == 0) {
        if (x <= MARGIN) {
            m_OnLeftEdge = true;
        }
        else if (x >= (m_windowWidth - MARGIN)) {
            m_OnRightEdge = true;
        }
    }
    else {
        m_OnLeftEdge = false;
        m_OnRightEdge = false;
    }

    if (DeltaY == 0) {
        if (y <= MARGIN) {
            m_OnUpperEdge = true;
        }
        else if (y >= (m_windowHeight - MARGIN)) {
            m_OnLowerEdge = true;
        }
    }
    else {
        m_OnUpperEdge = false;
        m_OnLowerEdge = false;
    }

    Update();
}

Cette fonction est utilisée pour notifier à la caméra que la souris a bougé. Les paramètres correspondent à la nouvelle position de la souris. Nous commençons par calculer la différence avec la position précédente pour les axes X et Y. Puis nous stockons ces valeurs pour le prochain appel de cette fonction. Nous mettons à jour les angles horizontal et vertical en divisant les deltas. J'utilise des facteurs qui fonctionnent bien pour moi, mais sur d'autres ordinateurs vous pouvez avoir besoin d'autres valeurs pour ces facteurs. Nous améliorerons cela dans un tutoriel à venir quand nous mettrons la fréquence d'images de l'application comme facteur.

Le groupe de tests suivant met à jour les indicateurs « m_On*Edge » en fonction de la position de la souris. Il y a une marge qui enclenche le comportement de « bord » si la souris s'approche d'un des bords de l'écran. Cette marge vaut par défaut 10 pixels. Enfin, nous appelons Update() pour recalculer les vecteurs cible et haut basés sur les nouvelles valeurs des angles horizontal et vertical.

camera.cpp
Sélectionnez
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
void Camera::OnRender()
{
    bool ShouldUpdate = false;

    if (m_OnLeftEdge) {
        m_AngleH -= 0.1f;
        ShouldUpdate = true;
    }
    else if (m_OnRightEdge) {
        m_AngleH += 0.1f;
        ShouldUpdate = true;
    }

    if (m_OnUpperEdge) {
        if (m_AngleV > -90.0f) {
            m_AngleV -= 0.1f;
            ShouldUpdate = true;
        }
    }
    else if (m_OnLowerEdge) {
        if (m_AngleV < 90.0f) {
            m_AngleV += 0.1f;
            ShouldUpdate = true;
        }
    }

    if (ShouldUpdate) {
        Update();
    }
}

Cette fonction est appelée à partir de la boucle principale. Nous en avons besoin pour les cas où la souris est placée sur les bords de l'écran et ne bouge pas. Dans ce cas, il n'y a pas d'événement de souris, mais nous voulons malgré tout que la caméra continue à bouger (jusqu'à ce que la souris quitte le bord). Nous vérifions si un des indicateurs est à TRUE et mettons à jour l'angle correspondant. S'il y a eu un changement dans un des angles, nous appelons Update() pour mettre à jour les vecteurs cible et haut. Quand la souris quitte l'écran, nous le détectons dans le gestionnaire d'événements souris et mettons l'indicateur à FALSE. Notez la façon de limiter l'angle vertical entre -90° et +90°. C'est pour empêcher de faire des tours complets en regardant en haut ou en bas.

camera.cpp
Sélectionnez
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
224.
225.
226.
227.
228.
229.
230.
231.
232.
233.
234.
void Camera::Update()
{
    const Vector3f Vaxis(0.0f, 1.0f, 0.0f);

    // Tourne le vecteur de vue avec l'angle horizontal autour de l'axe vertical
    Vector3f View(1.0f, 0.0f, 0.0f);
    View.Rotate(m_AngleH, Vaxis);
    View.Normalize();

    // Tourne le vecteur de vue avec l'angle vertical autour de l'axe horizontal
    Vector3f Haxis = Vaxis.Cross(View);
    Haxis.Normalize();
    View.Rotate(m_AngleV, Haxis);
    View.Normalize();

    m_target = View;
    m_target.Normalize();

    m_up = m_target.Cross(Haxis);
    m_up.Normalize();
}

Cette fonction met à jour les vecteurs cible et haut en fonction des angles vertical et horizontal. Nous commençons par un vecteur de vue dans l'état « reset ». Cela signifie qu'il est parallèle au sol (son angle vertical est zéro) et il regarde à droite (angle vertical à zéro, regardez l'image au-dessus). Nous mettons l'axe vertical vers le haut, puis nous tournons notre angle de vue autour, selon l'angle horizontal. Le résultat est un vecteur qui pointe dans la direction générale de la cible voulue, mais pas nécessairement à la bonne hauteur (par exemple, il est sur le plan XZ). En faisant un produit vectoriel de ce vecteur avec l'axe vertical, nous obtenons un autre vecteur sur le plan XZ, qui est orthogonal au plan créé par le vecteur vue et l'axe vertical. C'est notre nouvel axe horizontal et il est maintenant temps de tourner le vecteur vue autour de cet axe, en haut ou en bas, en fonction de l'angle vertical. Le résultat est le vecteur cible final et nous le mettons dans la variable membre correspondante. Maintenant, nous devons corriger le vecteur haut. Par exemple, si la caméra regarde en haut, le vecteur haut doit s'incliner vers l'arrière pour compenser (il doit être à 90° du vecteur cible). C'est similaire à la manière dont la tête s'incline en arrière lorsque l'on regarde haut dans le ciel. Le nouveau vecteur est calculé simplement, en faisant un autre produit vectoriel entre le vecteur cible final et l'axe horizontal. Si l'angle vertical vaut toujours zéro, alors le vecteur cible reste sur le plan XZ et le vecteur haut reste à (0, 1, 0). Si le vecteur cible est incliné en haut ou en bas, le vecteur haut va s'incliner en arrière ou en avant, respectivement.

tutorial15.cpp
Sélectionnez
209.
210.
glutGameModeString("1920x1200@32");
glutEnterGameMode();

Ces fonctions GLUT permettent à notre application de s'exécuter en plein écran dans le mode haute performance, appelé « mode de jeu ». Cela permet de tourner plus simplement la caméra sur 360°, car tout ce que nous avons à faire est amener la souris sur un des bords de l'écran. Notez que la résolution et le nombre de bits par pixel sont configurés en mode de jeu via une chaîne de caractères. 32 bits par pixel fournit le maximum de couleurs pour le rendu.

tutorial15.cpp
Sélectionnez
214.
pGameCamera = new Camera(WINDOW_WIDTH, WINDOW_HEIGHT);

La caméra est maintenant allouée dynamiquement à cet endroit, car elle effectue un appel GLUT (glutWarpPointer). Cet appel échouera si GLUT n'a pas encore été initialisé.

tutorial15.cpp
Sélectionnez
99.
100.
glutPassiveMotionFunc(PassiveMouseCB);
glutKeyboardFunc(KeyboardCB);

Ici, nous enregistrons deux nouvelles fonctions de callback. Une pour la souris et l'autre pour les touches classiques du clavier (le callback pour les touches spéciales récupère les flèches directionnelles et les touches des fonctions). « Passive motion » signifie que la souris se déplace sans qu'un seul de ses boutons soit appuyé.

tutorial.cpp
Sélectionnez
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
static void KeyboardCB(unsigned char Key, int x, int y)
{
    switch (Key) {
        case 'q':
            exit(0);
    }
}

static void PassiveMouseCB(int x, int y)
{
    pGameCamera->OnMouse(x, y);
}

Maintenant que nous utilisons le mode plein écran, il est plus difficile de quitter l'application. Le callback de clavier récupère l'appui sur la touche « q » pour quitter. Le callback de la souris transfère simplement la position de la souris à la caméra.

tutorial15.cpp
Sélectionnez
44.
45.
46.
static void RenderSceneCB()
{
    pGameCamera->OnRender();

À chaque fois que nous passons dans la boucle de rendu, nous devons en informer la caméra. Cela permet à celle-ci de tourner lorsque la souris ne bouge pas et est sur un bord de l'écran.

III. Sources▲

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources

IV. Remerciements▲

Merci à Etay Meiri de nous permettre de traduire son tutoriel.

Merci à LittleWhite pour ses corrections et à ClaudeLELOUP pour sa relecture.

Navigation▲

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+ inPartager

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 Etay Meiri. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -
  • Nous contacter
  • Participez
  • Hébergement
  • Informations légales

Copyright © 2000-2016 - www.developpez.com


Tutoriel 16 - Application de texture

Tutoriel 16 - Application basique de texture

Table des matières Plier Déplier

  • I. Contexte
  • II. Explication du code
  • III. Exercice
  • IV. Sources
  • V. Remerciements

Ce tutoriel présente le placage de textures.

L'application de texture consiste en l'application d'une image sur une ou plusieurs faces d'un modèle 3D. L'image (nommée « texture ») peut être n'importe quoi, mais représente généralement un motif, tel que des briques, du feuillage, de la terre, etc. Cela ajoute du réalisme à une scène. Comparons par exemple les images suivantes :

Image non disponible

Pour que le placage de textures fonctionne, nous avons besoin de trois choses : charger la texture dans OpenGL, fournir les coordonnées de texture avec les sommets (pour appliquer la texture dessus) et effectuer une opération d'échantillonnage à partir de la texture, en utilisant les coordonnées de texture, pour récupérer la couleur du pixel. Comme un triangle est mis à l'échelle, tourné, déplacé et enfin projeté, il peut apparaître sur l'écran d'une multitude de façons possibles et sembler différent, selon son orientation par rapport à la caméra. Le GPU doit faire en sorte que la texture suive le mouvement des sommets du triangle afin que cela semble réaliste (si la texture semble « nager » en travers du triangle, ça ne sera pas bien). Pour cela, le développeur fournit un ensemble de coordonnées, connu sous le nom de « coordonnées de texture » pour chaque sommet. Quand le GPU dessine le triangle, il interpole les coordonnées de texture sur toute la face du triangle et, dans le fragment shader, le développeur affecte ces coordonnées à la texture. Cette action est nommée « échantillonnage » et son résultat est un texel (pixel dans la texture). Le texel contient souvent une couleur qui est utilisée pour peindre le pixel correspondant à l'écran. Dans les tutoriels à venir, nous verrons que le texel peut contenir différents types de données qui peuvent être utilisées pour divers effets.

OpenGL supporte divers types de texture tels que 1D, 2D, 3D, cube… qui peuvent être utilisés dans des techniques différentes. Nous nous contenterons d'étudier les textures 2D pour l'instant. Une texture 2D possède une largeur et une hauteur qui peuvent valoir n'importe quelle valeur, dans la limite de la spécification. La multiplication de la largeur par la hauteur nous donne le nombre de texels de la texture. Comment pouvons-nous spécifier les coordonnées de texture d'un sommet ? Non, ce ne sont pas les coordonnées du texel dans la texture. Ce serait trop limitant, car remplacer la texture par une autre ayant des dimensions différentes signifie que nous devrons mettre à jour les coordonnées de texture de chaque sommet pour qu'elles correspondent à la nouvelle texture. Le scénario idéal est d'être capable de changer de texture sans changer les coordonnées de texture. Ainsi, les coordonnées de texture sont exprimées dans « l'espace texture », qui est simplement l'intervalle normalisé [0,1]. Ce qui signifie que les coordonnées de texture utilisent généralement une fraction et, en multipliant cette fraction par la hauteur/largeur correspondante, on récupère les coordonnées du texel dans la texture. Par exemple, si les coordonnées de texture sont [0.5, 0.1] et que la texture a pour largeur 320 et pour hauteur 200, la position du texel sera (160, 20) (0.5 * 320 = 160 et 0.1 * 200 = 20).

La convention habituelle est d'utiliser U et V comme axes de l'espace texture, où U correspond à l'axe X du système cartésien de coordonnées 2D, et V correspond à l'axe Y. OpenGL traite les valeurs des axes UV, de gauche à droite pour l'axe U et de bas en haut pour l'axe V. Regardons l'image suivante :

Image non disponible

Cette image représente l'espace texture et nous pouvons voir que l'origine de cet espace est le coin en bas à gauche. U augmente vers la droite et V augmente vers le haut. Considérons maintenant un triangle dont les coordonnées de texture sont définies comme sur l'image suivante :

Image non disponible

Disons que nous appliquons une texture de sorte que lorsque nous utilisons ces coordonnées de texture, nous voyons l'image de la petite maison à l'emplacement ci-dessus. Maintenant le triangle subit diverses transformations et lorsque vient le moment de le rastériser, il ressemble à ceci :

Image non disponible

Comme nous pouvons le voir, les coordonnées de texture « collent » aux sommets, car elles sont des attributs fondamentaux et ne changent pas avec les transformations. Lors de l'interpolation des coordonnées de texture, la plupart des pixels reçoivent la même coordonnée de texture que l'image originale (car ils sont restés à la même position, par rapport aux sommets) et, comme le triangle a été retourné, la texture a subi le même changement. Cela signifie que lorsque le triangle original est tourné, étiré ou écrasé, la texture suit sagement ces changements. Notons qu'il y a aussi des techniques, qui changent les coordonnées de texture, afin de déplacer la texture au travers de la face du triangle, de manière contrôlée. Cependant nos coordonnées resteront fixes pour l'instant.

Un autre concept important, associé au placage de texture est le « filtrage ». Nous avons discuté de comment associer les coordonnées de texture à un texel. La position du texel est toujours spécifiée en nombres entiers, mais qu'arrive-t-il lorsque les coordonnées de texture (qui sont, rappelons-le, des fractions entre 0 et 1) nous mappent un texel aux coordonnées (152.34, 745.14) ? La réponse triviale est de tronquer les valeurs pour obtenir (152, 745). Bien, cela fonctionne et produit des résultats adéquats, cependant, dans certains cas, cela n'est pas très joli. Une meilleure approche est de récupérer les texels en quadrilatères de deux sur deux ( (152, 745), (153, 745), (152, 746) et (153, 746) ), et de faire une interpolation linéaire entre leurs couleurs. Cette interpolation linéaire doit refléter la distance relative entre (152.34, 745.124) et chacun des autres texels. Plus la coordonnée est proche d'un texel, plus la participation de ce texel sur la couleur finale est importante, et plus elle est loin d'un texel, plus sa participation sera faible. Cela rend bien plus joli que l'approche initiale.

La méthode par laquelle la valeur du texel final est choisie est connue sous le nom de « filtrage ». La simple approche de troncation de la valeur est connue comme « filtrage au plus proche », et l'approche plus complexe que nous avons vue s'appelle « filtrage linéaire ». Un autre nom que vous pourrez rencontrer pour le filtre linéaire est « filtrage par point ». OpenGL supporte divers types de filtres, et vous avez la possibilité de choisir celui que vous voulez appliquer. Habituellement, les filtres qui fournissent les meilleurs résultats demandent plus de calculs au GPU, et peuvent donc avoir un effet sur la fréquence d'affichage. Le choix du type de filtrage est donc une question d'équilibrage, entre le résultat voulu et la capacité de la plate-forme cible.

Maintenant que nous comprenons le système de coordonnées de texture, il est temps d'étudier la manière dont le placage de texture est effectué avec OpenGL. L'application de texture signifie, dans OpenGL, la manipulation des connexions imbriquées de quatre concepts :

  • la texture ;
  • l'unité de texture ;
  • l'objet d'échantillonnage ;
  • la variable uniforme de l'échantillonneur dans le shader.

La texture contient les données de l'image elle-même (par exemple : les texels). La texture peut être de divers types (1D, 2D, etc.), avec diverses résolutions et le type de données sous-jacent peut être dans divers formats (RGB, RGBA, etc.). OpenGL fournit une manière de définir le point d'entrée des données en mémoire, ainsi que tous les attributs précédents et de charger ces données dans le GPU. Il y a aussi de multiples paramètres que nous pouvons contrôler, tels que le type de filtrage, etc. D'une manière très proche de celle utilisée pour les tampons de sommets, l'objet de texture est associé à un identifiant. Après la création de cet identifiant et le chargement des données de la texture ainsi que de ses paramètres, nous pouvons facilement échanger les textures, à la volée en liant les différents identifiants dans l'état OpenGL. Nous n'avons plus besoin de charger les données. À partir de maintenant, c'est au pilote OpenGL de s'assurer que les données sont chargées à temps sur le GPU, avant que le rendu ne commence.

La texture n'est pas liée directement dans le shader (où l'échantillonnage s'effectue réellement). À la place, elle est liée à une « unité de texture » dont l'index est donné au shader. Donc le shader accède à la texture au travers de l'unité de texture. Il y a généralement de multiples unités de texture disponibles, leur nombre exact dépendant des capacités de la carte graphique. Afin de lier une texture A à l'unité de texture 0 nous devons d'abord activer cette unité 0 puis lui lier la texture A. Nous pouvons maintenant activer l'unité de texture 1 pour lui lier une texture différente (ou la même d'ailleurs). L'unité de texture 0 reste liée à la texture A.

Il y a une petite astuce, dans le fait que chaque unité de texture peut être liée à plusieurs textures, tant que ces textures sont de types différents. C'est appelé la « cible » de la texture. Lorsque nous lions une texture à une unité de texture, nous spécifions la cible (1D, 2D, etc.). Nous pouvons donc avoir une texture A liée à la cible 1D alors que la texture B est liée à la cible 2D, et ce sur la même unité de texture.

L'opération d'échantillonnage a (généralement) lieu dans le fragment shader et il existe une fonction spéciale qui le fait. La fonction d'échantillonnage doit connaître l'unité de texture à laquelle elle doit accéder, car nous pouvons échantillonner plusieurs unités de texture dans un même fragment shader. Il y a un groupe de variables uniformes particulières pour cela, selon la cible de la texture : « sampler1d », « sampler2D », « sampler3D », « samplerCube », etc. Nous pouvons créer autant de variables uniformes d'échantillonnage que nous le souhaitons, et affecter à chacune la valeur d'une unité de texture, à partir de l'application. À chaque fois que nous appelons la fonction d'échantillonnage sur une variable uniforme d'échantillonnage, l'unité de texture correspondante (et la texture) sera utilisée.

Le dernier concept est l'objet d'échantillonnage, à ne pas confondre avec la variable uniforme d'échantillonnage ! Ce sont deux entités séparées. La texture contient aussi bien les données de la texture, que les paramètres qui configurent l'opération d'échantillonnage. Ces paramètres font partie de l'état d'échantillonnage. Cependant, nous pouvons créer un objet d'échantillonnage, le configurer avec un état d'échantillonnage, puis le lier à l'unité de texture. Lorsque nous faisons ainsi, l'objet d'échantillonnage remplace tous les états définis dans l'objet de texture. Pas d'inquiétude, pour l'instant nous n'utiliserons pas les objets d'échantillonnage, mais il est intéressant de savoir qu'ils existent.

Le digramme suivant résume les relations entre les différents concepts d'application de texture que nous venons d'apprendre :

Image non disponible

II. Explication du code▲

OpenGL sait comment charger les données d'une texture de différents formats, à partir d'une adresse mémoire, mais ne fournit aucun moyen de charger la texture en mémoire, à partir de fichiers d'images tels que PNG ou JPG. Nous allons utiliser une bibliothèque externe à cette fin. Il y a de nombreuses options pour cela et nous allons utiliser ImageMagick, une bibliothèque libre, qui supporte de nombreux types d'images et est portable sur divers systèmes d'exploitation.

La majeure partie de la gestion de texture est encapsulée dans la classe suivante :

ogldev_texture.h
Sélectionnez
27.
28.
29.
30.
31.
32.
33.
34.
35.
class Texture
{
public:
    Texture(GLenum TextureTarget, const std::string& FileName);

    bool Load();

    void Bind(GLenum TextureUnit);
};

Lors de la création d'un objet Texture, nous devons définir une cible (nous utilisons GL_TEXTURE_2D) et un nom de fichier. Après cela, nous appelons la fonction Load(). Elle peut échouer, par exemple, si le fichier n'existe pas ou si ImageMagick a rencontré une erreur quelconque. Lorsque nous voulons utiliser une instance spécifique de Texture, nous devons la lier à une des unités de texture.

ogldev_texture.cpp
Sélectionnez
31.
32.
33.
34.
35.
36.
37.
38.
try {
    m_pImage = new Magick::Image(m_fileName);
    m_pImage->write(&m_blob, "RGBA");
}
catch (Magick::Error& Error) {
    std::cout << "Error loading texture '" << m_fileName << "': " << Error.what() << std::endl;
    return false;
}

Voici comment nous utilisons ImageMagick pour charger la texture depuis une image, puis la préparons en mémoire pour être chargée avec OpenGL. Nous commençons par charger un membre de classe dont le type est Magick::Image, en utilisant le nom de fichier de la texture. Cet appel charge la texture dans une représentation mémoire interne à ImageMagick et inutilisable directement par OpenGL. Ensuite nous écrivons l'image dans un objet de type Magick::Blob, en utilisant le format RVBA (rouge, vert, bleu et alpha). Le BLOB (« Binary Large Object ») est un mécanisme utile pour stocker l'image encodée en mémoire, de manière à pouvoir l'utiliser dans des programmes externes. S'il y a une erreur, une exception sera lancée, donc nous devons nous y préparer.

ogldev_texture.cpp
Sélectionnez
40.
glGenTextures(1, &m_textureObj);

Cette fonction OpenGL est similaire à glGenBuffers()Gluint avec laquelle nous sommes déjà familiers. Elle génère le nombre demandé de textures et place leur identifiant dans le pointeur sur tableau de Gluint donné (le second paramètre). Dans notre cas, nous avons juste besoin d'une texture.

ogldev_texture.cpp
Sélectionnez
41.
glBindTexture(m_textureTarget, m_textureObj);

Nous allons faire plusieurs appels relatifs aux textures et de manière similaire à ce que nous faisions déjà avec les tampons de sommets, nous devons dire à OpenGL sur quel objet il opère. C'est le but de la fonction glBindTexture()GL_TEXTURE_1DGLTEXTURE_2DGL_TEXTURE_2D. Elle dit à OpenGL quel est l'objet auquel nous nous référons dans tous les appels relatifs aux textures, jusqu'à ce qu'une nouvelle texture soit liée. En plus de l'identifiant (second paramètre), nous spécifions aussi la cible de la texture, qui peut valoir GL_TEXTURE_1D, GLTEXTURE_2D, etc. Il peut y avoir une texture différente liée à chacune des cibles simultanément. Dans notre implémentation, la cible est donnée en tant que paramètre du constructeur (et pour l'instant nous utilisons GL_TEXTURE_2D).

ogldev_texture.cpp
Sélectionnez
42.
glTexImage2D(m_textureTarget, 0, GL_RGBA, m_pImage->columns(), m_pImage->rows(), 0, GL_RGBA, GL_UNSIGNED_BYTE, m_blob.data());

Cette fonction, assez complexe, est utilisée afin de charger la partie principale de la texture, c'est-à-dire les données elles-mêmes. Il y a plusieurs fonctions glTexImage* disponibles, chacune couvrant quelques types de cibles de texture. La cible de la texture est toujours le premier paramètre. Le second paramètre est le LOD ou « Level Of Detail ». Une texture peut contenir la même image dans des résolutions différentes, un concept connu sous le nom de « mip-mapping ». Chaque « mip-map » possède un index de LOD différent, en partant de 0 jusqu'à la résolution la plus haute, croissant alors que la résolution décroit. Pour l'instant nous avons un seul « mip-map » donc nous donnons zéro.

Le paramètre suivant est le format interne dans lequel OpenGL stocke la texture. Par exemple, nous donner une texture avec le panel de couleurs complet (rouge, vert, bleu et alpha), mais si nous spécifions GL_RED, nous aurons une texture avec juste le canal rouge, ce qui apparaîtra… rouge (essayez !). Nous utilisons GL_RGBA pour récupérer toutes les couleurs de la texture correctement. Les deux paramètres suivants sont la largeur et la hauteur de la texture, en texels. La bibliothèque ImageMagick stocke gentiment ces informations pour nous lorsqu'elle charge l'image, nous pouvons les récupérer en utilisant les fonctions Image::columns()/rows(). Le cinquième paramètre est la bordure, que nous laissons à zéro pour le moment.

Les trois derniers paramètres spécifient la source des données en entrée. Les paramètres sont le format, le type et l'adresse mémoire. Le format nous indique le nombre de canaux et doit correspondre au BLOB que nous avons en mémoire. Le type décrit le type de données que nous avons par canal. OpenGL supporte de nombreux types de données, mais le BLOB ImageMagick définit un octet par canal, donc nous utilisons GL_UNSIGNED_BYTE. Enfin vient l'adresse mémoire des données que nous extrayons depuis le BLOB, en utilisant la fonction Blob::data().

ogldev_texture.cpp
Sélectionnez
43.
44.
glTexParameterf(m_textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(m_textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

La fonction générale glTexParameterf() contrôle de nombreux aspects de l'opération d'échantillonnage. Ces aspects font partie de l'état d'échantillonnage de la texture. Ici nous spécifions le filtre à utiliser pour le grossissement et le rétrécissement. Chaque texture possède une largeur et une hauteur, mais celles-ci sont rarement appliquées à un triangle dans les mêmes proportions. Dans la plupart des cas le triangle est soit plus petit soit plus grand que la texture. Dans ces cas le type de filtre détermine comment gérer le cas du grossissement ou du rétrécissement de la texture pour qu'elle corresponde aux proportions du triangle. Quand le triangle rastérisé est plus grand que la texture (par exemple : très proche de la caméra), nous pouvons avoir plusieurs pixels couverts par le même texel (grossissement). Quand il est plus petit (par exemple : très loin de la caméra), plusieurs texels peuvent être couverts par le même pixel (rétrécissement). Ici nous choisissons le filtre d'interpolation linéaire pour les deux cas. Comme nous l'avons vu précédemment, l'interpolation linéaire fournit des résultats ayant un bon rendu, en mélangeant les couleurs d'un carré de 2x2 texels, basé sur la proximité du texel actuel (calculé en multipliant les coordonnées de texture par les dimensions de la texture).

ogldev_texture.cpp
Sélectionnez
49.
50.
51.
52.
53.
void Texture::Bind(GLenum TextureUnit)
{
   glActiveTexture(TextureUnit);
   glBindTexture(m_textureTarget, m_textureObj);
}

Comme notre application 3D devient plus complexe, nous pouvons utiliser plusieurs textures différentes dans divers appels de dessin dans la boucle de rendu. Avant chaque appel à la fonction de dessin, nous devons lier la texture que nous voulons à une des unités de texture afin qu'elle soit échantillonnée dans le fragment shader. Cette fonction prend l'index de l'unité de texture (GL_TEXTURE0, GL_TEXTURE1, etc.) comme paramètre. Elle l'active en utilisant la fonction glActiveTexture()Texture::Bind() puis lui lie la texture. Cette texture restera liée à cette unité de texture jusqu'au prochain appel de Texture::Bind() avec la même unité de texture.

shader.vs
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
#version 330

layout (location = 0) in vec3 Position;
layout (location = 1) in vec2 TexCoord;

uniform mat4 gWVP;

out vec2 TexCoord0;

void main()
{
   gl_Position = gWVP * vec4(Position, 1.0);
   TexCoord0 = TexCoord;
};

Ceci est le code du vertex shader mis à jour. Il y a un paramètre additionnel, nommé TexCoord qui est un vecteur 2D. Au lieu de définir la couleur de sortie, ce shader transfère les coordonnées de texture vers le fragment shader, sans y toucher. La rastérisationva effectuer une interpolation des coordonnées de texture à travers la face du triangle et chaque fragment shader sera appelé avec ses propres coordonnées de texture.

shader.fs
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
in vec2 TexCoord0;

out vec4 FragColor;

uniform sampler2D gSampler;

void main()
{
    FragColor = texture2D(gSampler, TexCoord0.st);
};

Et voici le code du fragment shader mis à jour. Il possède une variable en entrée appelée TexCoord0 qui contient les coordonnées de texture interpolées, que nous récupérons depuis le vertex shader. On trouve une variable uniforme nommée gSampler qui a comme type sampler2D. L'application doit définir comme valeur pour cette variable l'index de l'unité de texture, afin que le fragment shader soit capable d'accéder à la texture. La fonction principale fait une seule chose : elle utilise la fonction interne texture2D pour échantillonner la texture. Le premier paramètre est la variable uniforme de l'échantillonneur et le second paramètre reçoit les coordonnées de texture. La valeur retournée est le texel échantillonné (qui dans notre cas contient une couleur), après avoir été filtré. C'est la couleur finale du pixel dans ce tutoriel. Dans les prochains tutoriels, nous verrons que l'éclairage change simplement cette couleur, en se basant sur les paramètres des sources lumineuses.

Télécharger Sélectionnez
128.
129.
130.
131.
132.
Vertex Vertices[4] = {
    Vertex(Vector3f(-1.0f, -1.0f, 0.5773f), Vector2f(0.0f, 0.0f)),
    Vertex(Vector3f(0.0f, -1.0f, -1.15475), Vector2f(0.5f, 0.0f)),
    Vertex(Vector3f(1.0f, -1.0f, 0.5773f), Vector2f(1.0f, 0.0f)),
    Vertex(Vector3f(0.0f, 1.0f, 0.0f), Vector2f(0.5f, 1.0f)) };

Jusqu'à ce tutoriel, notre tampon de sommets était une simple suite de Vector3f contenant les positions. Maintenant nous avons la structure Vertex qui contient aussi les coordonnées de texture en tant que Vector2f.

tutorial16.cpp
Sélectionnez
80.
81.
82.
83.
84.
85.
86.
87.
88.
...
glEnableVertexAttribArray(1);
...
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)12);
...
pTexture->Bind(GL_TEXTURE0);
...
glDisableVertexAttribArray(1);

Voici les changements et ajouts effectués au niveau de la boucle de rendu. Nous commençons par activer l'attribut de sommet 1 pour les coordonnées de texture, en plus de l'attribut 0 qui est déjà activé pour la position. Cela correspond à la disposition des déclarations dans le vertex shader. Ensuite nous appelons glVertexAttribPointerVertexsizeof( Vector3f )VertexVertexGlvoid* pour définir la position des coordonnées de texture dans le tampon de sommets. Une coordonnée de texture est composée de deux valeurs en nombre flottant, ce qui correspond aux deuxième et troisième paramètres. Regardons le cinquième paramètre. Il s'agit de la taille de la structure Vertex et il est spécifié pour la position et les coordonnées de texture. Ce paramètre est connu sous le nom de « pas » et dit à OpenGL le nombre d'octets entre le début d'un attribut d'un sommet et le début du même attribut dans le sommet suivant. Dans notre cas, le tampon contient : position0, texture0, position1, texture1, etc. Dans les tutoriels précédents, nous avions uniquement la position dans le tampon, nous pouvions donc sans soucis mettre ce paramètre à zéro ou sizeof( Vector3f ). Maintenant que nous avons plus d'un attribut, le pas peut uniquement valoir le nombre d'octets de la structure Vertex. Le dernier paramètre est le décalage en octets, depuis le début de la structure Vertex, jusqu'aux attributs de texture. Nous devons faire la conversion en Glvoid*, car c'est ainsi que la fonction attend de recevoir ce décalage.

Avant l'appel à la fonction de dessin, nous devons lier la texture que nous voulons utiliser à une unité de texture. Nous avons une seule texture donc n'importe quelle unité suffirait. Nous avons seulement besoin de nous assurer que la même unité de texture est définie dans le shader (voir plus bas). Après l'appel à la fonction de dessin, nous désactivons l'attribut.

tutorial16.cpp
Sélectionnez
253.
254.
255.
glFrontFace(GL_CW);
glCullFace(GL_BACK);
glEnable(GL_CULL_FACE);

Ces appels OpenGL ne sont pas vraiment relatifs à l'application de texture, je les ai juste ajoutés afin que cela rende plus joli (essayez de les désactiver…). Ils activent l'élimination des faces cachées, une optimisation commune, utilisée afin d'ignorer des triangles avant le lourd procédé de rastérisation. La motivation ici est qu'il y a souvent 50 % de la surface d'un objet qui nous est cachée (le dos d'une personne, maison, voiture, etc.). La fonction glFrontFace() () dit à OpenGL, que les sommets du tampon de sommets sont ordonnés dans le sens des aiguilles d'une montre. C'est-à-dire, lorsque nous regardons la face avant du triangle, nous trouvons les sommets dans le tampon rangés dans le sens des aiguilles d'une montre. La fonction glCullFace() dit au GPU d'ignorer les triangles à l'arrière du triangle. Cela signifie que l'intérieur d'un objet n'a pas besoin d'être rendu, seulement l'extérieur. Enfin l'élimination des faces arrière en elle-même est activée (par défaut elle est désactivée). Notons que, dans ce tutoriel, j'ai inversé l'ordre des index dans le triangle du bas. La manière dont c'était fait auparavant faisait apparaître le triangle comme s'il était tourné vers l'intérieur de la pyramide (ligne 170 de tutorial16.cpp).

tutorial16.cpp
Sélectionnez
264.
glUniform1i(gSampler, 0);

Ici nous définissons l'index de l'unité de texture que nous allons utiliser, dans la variable uniforme d'échantillonnage du shader. gSampler est une variable uniforme dont la valeur a été récupérée auparavant, en utilisant la fonction glGetUniformLocation()GL_TEXTURE0. Il est important de noter que l'index effectif de l'unité de texture est utilisé ici et pas l'énumération GL_TEXTURE0 (qui a une valeur différente).

tutorial16.cpp
Sélectionnez
264.
265.
266.
267.
268.
pTexture = new Texture(GL_TEXTURE_2D, "test.png");

if (!pTexture->Load()) {
    return 1;
}

Ici nous créons l'objet Texture et le chargeons. « test.png » est inclus avec les sources de ce tutoriel, mais ImageMagick devrait être capable de gérer presque n'importe quel fichier que nous y mettrions.

III. Exercice▲

Si vous exécutez le code fourni avec ce tutoriel, vous remarquerez que les faces de la pyramide ne sont pas identiques. Essayez de comprendre ce qu'il se passe et ce qui devrait être modifié pour les rendre identiques.

IV. Sources▲

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources

V. Remerciements▲

Merci à Etay Meiri de nous permettre de traduire son tutoriel.

Merci à LittleWhite pour ses corrections, à kimz et Claude LELOUP pour leur relecture orthographique.

Navigation▲

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+ inPartager

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 Etay Meiri. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite - Contacter par email
  • Nous contacter
  • Participez
  • Hébergement
  • Informations légales

Copyright © 2000-2016 - www.developpez.com


Tutoriel 17 - Éclairage ambiant

Table des matières Plier Déplier

  • I. Contexte
  • II. Explication du code
  • III. Sources
  • IV. Remerciements

Ce tutoriel est le premier de la série sur l'éclairage.

L'éclairage est un des sujets les plus importants dans le domaine de la 3D. Le modéliser correctement ajoute beaucoup à l'aspect visuel de la scène rendue. La raison du mot « modéliser » vient du fait que nous ne pouvons pas simuler exactement ce que fait la nature. La lumière réelle est faite d'une énorme quantité de particules, appelées « photons », qui agissent à la fois comme des ondes et des particules (la dualité onde-particule de la lumière). Si nous essayons de calculer l'effet de chaque photon dans notre programme, nous allons très rapidement être à court de puissance de calcul.

Par conséquent divers modèles d'éclairage ont été développés au fil des années pour reproduire l'effet principal de la lumière sur les objets qu'elle atteint, en les rendant visibles. Ces modèles d'éclairage sont devenus de plus en plus complexes, au fur et à mesure de la progression des connaissances en rendu 3D et de l'augmentation de la puissance de calcul. Pendant l'étude des quelques tutoriels suivants, nous verrons le modèle d'éclairage de base, plus simple à implémenter, mais qui contribue énormément à l'atmosphère globale d'une scène.

Le modèle d'éclairage de base est appelé « Ambiante/Diffuse/Spéculaire ». La lumière ambiante est le type de lumière que l'on voit lorsque nous allons dehors, en un jour de soleil habituel. Même si le soleil traverse le ciel et que les rayons lumineux qu'il projette arrivent à différents angles, au cours de la journée, la plupart des choses seront visibles, même dans l'ombre. Comme la lumière rebondit sur chaque chose touchée, elle finit par tout toucher, ainsi les objets qui ne sont pas directement face au soleil finissent par être éclairés. Même une ampoule dans une pièce agit comme le soleil en ce sens et répand une lumière ambiante, car si la pièce n'est pas trop grande, tout est éclairé uniformément. La lumière ambiante est modélisée comme une lumière sans origine, sans direction et qui a un effet identique sur tous les objets de la scène.

La lumière diffuse accentue le fait que l'angle dans lequel la lumière touche la surface affecte l'intensité de l'éclairage de l'objet. Quand la lumière atteint un objet sur un côté, ce côté est plus clair que l'autre (celui qui n'est pas directement en face de la lumière). Nous venons de voir que le soleil répand une lumière ambiante qui n'a pas de direction spécifique. Cependant, la lumière du soleil a aussi des propriétés de diffusion. Quand elle touche un bâtiment, nous pouvons généralement observer qu'un côté du bâtiment est plus éclairé qu'un autre. La propriété la plus importante de la lumière diffuse est sa direction.

La lumière spéculaire est plus une propriété de l'objet, que de la lumière elle-même. C'est ce qui fait que des portions d'objets brillent, lorsque la lumière les atteint dans un angle spécifique et que l'on regarde depuis un point spécifique. Les objets métalliques ont souvent une propriété spéculaire. Par exemple, une voiture pendant une journée ensoleillée brille parfois au niveau de ses arêtes. Pour calculer la lumière spéculaire, nous devons prendre en considération la direction d'arrivée de la lumière (et de son rebond), ainsi que la position de l'observateur.

Dans les applications 3D, nous ne créons généralement pas directement de lumière ambiante, diffuse ou spéculaire. Nous utilisons plutôt des sources lumineuses tels le soleil (en extérieur), une ampoule (à l'intérieur) ou une lampe de poche (dans une grotte). Ces sources lumineuses peuvent avoir différentes combinaisons d'intensités ambiante, diffuse et spéculaire, ainsi que d'autres propriétés spécifiques. Par exemple la lampe de poche a un cône lumineux et les objets qui en sont éloignés ne sont pas éclairés du tout.

Dans les tutoriels suivants, nous développerons quelques types de sources lumineuses très utiles et étudierons le modèle d'éclairage de base, tout en progressant.

Nous allons commencer par une source lumineuse appelée « lumière directionnelle ». Une lumière directionnelle a une direction, mais pas d'origine spécifique. Cela signifie que tous les rayons lumineux sont parallèles les uns aux autres. La direction d'une lumière est définie par un vecteur et ce vecteur est utilisé pour calculer la lumière sur tous les objets de la scène, quelle que soit leur position. Le soleil rentre particulièrement bien dans la catégorie des lumières directionnelles. Si nous essayons de calculer l'angle précis avec lequel la lumière du soleil atteint deux bâtiments mitoyens, nous retrouverons deux valeurs presque identiques (la différence entre elles sera une fraction minuscule). C'est parce que le soleil est situé à quelque 150 millions de kilomètres de distance. Par conséquent, nous ne nous attardons pas sur sa position et prenons uniquement la direction en compte.

Une autre propriété importante d'une lumière directionnelle est que l'éclairage reste le même, quelle que soit la distance de l'objet éclairé. C'est une différence avec une autre source lumineuse que nous étudierons dans les tutoriels à venir, la lumière ponctuelle, dont l'éclairage est de plus en plus faible avec l'éloignement (par exemple : une ampoule).

L'image suivante illustre une lumière directionnelle :

Image non disponible

Nous venons de voir que la lumière du soleil possède des propriétés ambiantes ainsi que de diffusion. Nous allons développer la partie ambiante ici et la partie diffusion dans le tutoriel suivant.

Dans le tutoriel précédent, nous avons appris comment échantillonner la couleur d'un pixel à partir d'une texture. La couleur possède trois canaux (rouge, vert et bleu) et chaque canal est représenté par un octet. Cela signifie que la valeur d'un canal peut aller de 0 à 255. Différentes combinaisons de canaux représentent donc différentes couleurs. Quand tous les canaux sont à zéro, la couleur est noire. Quand ils sont tous à 255, la couleur est blanche. Toutes les autres couleurs se situent entre les deux. En multipliant tous les canaux par la même valeur, nous obtenons la même couleur de base, mais pouvons la rendre plus claire ou plus sombre (en fonction de la valeur).

Lorsque la lumière blanche atteint une surface, la couleur reflétée est simplement la couleur de la surface. Elle peut être plus claire ou plus sombre, en fonction de la puissance de la source lumineuse, mais cela reste la même couleur de base. Si la source lumineuse est rouge pur (255, 0, 0), la couleur reflétée ne pourra être qu'une sorte de rouge. C'est parce que la lumière n'a pas de canaux vert et bleu qui pourraient être reflétés par la surface. Si la surface est bleu pur, le résultat sera donc noir. La conclusion est que la lumière peut uniquement exposer la couleur d'un objet, pas le « peindre ».

Nous allons définir la couleur des sources lumineuses comme un trio de nombres flottants dans l'intervalle [0,1]. En multipliant la couleur de la lumière par la couleur d'un objet, nous obtiendrons la couleur reflétée. Cependant, nous souhaitons aussi prendre en compte l'intensité ambiante de la lumière. Par conséquent, l'intensité ambiante sera définie par un simple nombre flottant dans l'intervalle [0,1], qui sera lui aussi multiplié par tous les canaux de la lumière reflétée que nous venons de calculer. Ce sera la couleur finale. L'équation suivante résume le calcul de la lumière ambiante :

(RpGpBp)=couleur pixel
⎛⎝⎜RpGpBp⎞⎠⎟=couleur pixel
(RlGlBl)=couleur lumière
A=intensité ambiante
(RpGpBp)×(RlGlBl)×(AAA)=(RpRlAGpGlABpBlA)=couleur finale du pixel

Dans le code d'exemple de ce tutoriel, nous pourrons jouer avec les touches « a » et « s », pour augmenter ou réduire l'intensité de la lumière ambiante et voir l'effet que ça a sur la pyramide texturée du tutoriel précédent. Comme ce n'est que la partie ambiante de la lumière directionnelle, la direction elle-même n'a pas encore été impliquée. Cela changera dans le prochain tutoriel où nous étudierons la lumière diffuse. Pour l'instant nous allons voir que la pyramide est éclairée de la même manière, quelle que soit la manière dont on la regarde.

La lumière ambiante est considérée par beaucoup comme quelque chose à éviter autant que possible. C'est parce qu'elle semble quelque peu artificielle et la simplicité de son implémentation ne contribue pas vraiment au réalisme de la scène. En utilisant des méthodes avancées, telles que l'illumination globale, on peut éliminer le besoin de lumière ambiante, car la lumière qui est reflétée sur des objets et qui atteint d'autres objets peut aussi être prise en compte. Comme nous ne sommes pas encore là, nous aurons généralement besoin d'un peu de lumière ambiante, pour éviter les cas où un côté d'un objet est éclairé et l'autre côté est dans le noir complet. Pour simuler une lumière de fin de journée, il faut beaucoup jouer avec les paramètres et beaucoup de travail d'affinage.

II. Explication du code▲

Nos exemples de code vont devenir de plus en plus complexes avec le temps et cette tendance va s'accentuer. Dans ce tutoriel, en plus d'implémenter l'éclairage ambiant, nous allons aussi restructurer le code. Cela va placer le code dans une meilleure forme pour les tutoriels à venir. Les changements majeurs sont :

  1. encapsulation de la gestion des shaders dans la classe Technique. Cela inclut les actions telles que la compilation et la liaison. À partir de maintenant, nous implémenterons nos effets visuels dans des classes dérivées de la classe Technique ;
  2. déplacement de l'initialisation de GLUT et de la gestion des callbacks, dans le composant GLUTBackend. Ce composant s'enregistre lui-même pour recevoir les appels aux callbacks GLUT et les transfère à l'application, en utilisant une interface C++ appelée ICallbacks ;
  3. déplacements des fonctions et variables globales, depuis le ficher cpp principal vers une classe, qui peut être considérée comme « l'application ». Dans le futur, nous étendrons ceci à une classe de base pour toutes les applications et fournirons les fonctionnalités communes à toutes. Cette approche est très populaire dans de nombreux moteurs de jeux et frameworks.

La majorité du code de ce tutoriel (excepté le code spécifique à la lumière) n'est pas neuf, mais a juste été réorganisé conformément aux principes du plan décrit ci-dessus. Par conséquent, seuls les nouveaux fichiers d'en-têtes sont revus.

Télécharger Sélectionnez
24.
25.
26.
void GLUTBackendInit(int argc, char** argv);

bool GLUTBackendCreateWindow(unsigned int Width, unsigned int Height, unsigned int bpp, bool isFullScreen, const char* pTitle);

Beaucoup de code spécifique à GLUT a été déplacé vers le composant « GLUT backend », qui rend plus faciles l'initialisation de GLUT et la création d'une fenêtre en utilisant les fonctions ci-dessus.

Télécharger Sélectionnez
28.
void GLUTBackendRun(ICallbacks* pCallbacks);

Après que GLUT a été initialisé et qu'une fenêtre a été créée, l'étape suivante est d'exécuter la boucle principale de GLUT, dans la fonction suivante. L'ajout ici est l'utilisation de l'interface ICallbacks, qui aide à l'enregistrement des fonctions de callback GLUT. Au lieu de demander à chaque application d'enregistrer ses callbacks de son côté, le composant « GLUT backend » enregistre ses propres fonctions privées et transmet les événements à l'objet spécifié dans l'appel à la fonction ci-dessus. La classe principale de l'application va souvent implémenter de son côté cette interface et se passer elle-même en tant que paramètre de l'appel à GLUTBackendRun. Cette approche a aussi été choisie pour ce tutoriel.

Télécharger Sélectionnez
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
class Technique
{
public:

   Technique();

   ~Technique();

   virtual bool Init();

   void Enable();

protected:

   bool AddShader(GLenum ShaderType, const char* pShaderText);

   bool Finalize();

   GLint GetUniformLocation(const char* pUniformName);

private:

   GLuint m_shaderProg;

   typedef std::list<GLuint> ShaderObjList;
   ShaderObjList m_shaderObjList;
};

Dans les tutoriels précédents, toute la corvée de compiler et lier les shaders était le lot de l'application. La classe Technique aide en intégrant toutes les fonctionnalités communes et en permettant aux classes dérivées de se concentrer sur le cœur de l'effet (la technique).

Chaque technique doit d'abord être initialisée en appelant la fonction Init(). Une technique dérivée doit appeler Init() de la classe de base (qui crée l'objet du programme OpenGL) et peut ajouter ses initialisations privées ici.

Après qu'un objet Technique a été créé et initialisé, la séquence habituelle pour la classe de technique dérivée est d'appeler la fonction protégée AddShader() sur autant de shaders GLSL (fournis dans une chaîne de caractères) que nécessaire. Enfin, Finalize() est appelée pour lier les objets. La fonction Enable() ne fait pour l'instant qu'appeler glUseProgram() () et doit donc être appelée à chaque fois que l'on change de technique et que l'on appelle la fonction de dessin.

Cette classe garde les objets intermédiaires compilés et les détruit après la liaison, en utilisant glDeleteShader(). Cela aide à réduire le nombre de ressources consommées par notre application. Pour de meilleures performances, les applications OpenGL compilent souvent tous les shaders pendant le chargement et non pendant l'exécution. En supprimant les objets directement après la liaison, nous aidons à garder basse la consommation de ressources OpenGL par notre application. L'objet du programme lui-même est supprimé dans le destructeur, en utilisant glDeleteProgram().

Télécharger Sélectionnez
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
class Tutorial17 : public ICallbacks
{
public:

    Tutorial17()
    {
        ...
    }

    ~Tutorial17()
    {
        ...
    }

    bool Init()
    {
        ...
    }

    void Run()
    {
       GLUTBackendRun(this);
    }

    virtual void RenderSceneCB()
    {
        ...
    }

    virtual void IdleCB()
    {
        ...
    }

    virtual void SpecialKeyboardCB(int Key, int x, int y)
    {
        ...
    }

    virtual void KeyboardCB(unsigned char Key, int x, int y)
    {
        ...
    }

    virtual void PassiveMouseCB(int x, int y)
    {
        ...
    }

private:

    void CreateVertexBuffer()
    {
        ...
    }
    void CreateIndexBuffer()
    {
        ...
    }

    GLuint m_VBO;
    GLuint m_IBO;
    LightingTechnique* m_pEffect;
    Texture* m_pTexture;
    Camera* m_pGameCamera;
    float m_scale;
    DirectionalLight m_directionalLight;
};

C'est un squelette de la classe de l'application principale, qui encapsule le code restant qui nous est familier. Init() prend en charge la création de l'effet, le chargement de la texture et la création des tampons d'indices et de sommets. Run() appelle GLUTBackendRun() et passe l'instance elle-même en paramètre. Comme cette classe implémente l'interface ICallbacks, tous les événements GLUT sont traités dans les bonnes méthodes de la classe. De plus, toutes les variables, qui étaient préalablement dans la section globale du fichier, sont maintenant des attributs privés de la classe.

Télécharger Sélectionnez
25.
26.
27.
28.
29.
struct DirectionalLight
{
    Vector3f Color;
    float AmbientIntensity;
};

C'est le début de la définition de la lumière directionnelle. Pour l'instant, seule la partie ambiante existe et la direction est absente. Nous ajouterons la direction dans le tutoriel suivant, lorsque nous traiterons la lumière diffuse. La structure contient deux champs : une couleur et l'intensité ambiante. La couleur détermine quels canaux de couleur des objets seront reflétés et en quelle intensité. Par exemple, si la couleur vaut (1.0, 0.5, 0.0), alors le canal rouge de l'objet sera complètement reflété, le canal vert sera reflété à moitié et le canal bleu ne sera pas reflété du tout. C'est parce qu'un objet ne peut que refléter la lumière qui l'atteint (les sources lumineuses sont différentes, elles émettent la lumière et doivent être gérées séparément). Dans le cas du soleil, la couleur habituellement choisie est le blanc pur (1.0, 1.0, 1.0).

AmbientIntensity définit à quel point la lumière est sombre ou claire. Nous pouvons avoir une lumière blanche dont l'intensité est 1.0, donc l'objet est complètement éclairé, ou une intensité de 0.1 qui signifie que l'objet sera visible, mais apparaîtra très sombre.

Télécharger Sélectionnez
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
class LightingTechnique : public Technique
{
public:

    LightingTechnique();

    virtual bool Init();

    void SetWVP(const Matrix4f& WVP);
    void SetTextureUnit(unsigned int TextureUnit);
    void SetDirectionalLight(const DirectionalLight& Light);

private:

    GLuint m_WVPLocation;
    GLuint m_samplerLocation;
    GLuint m_dirLightColorLocation;
    GLuint m_dirLightAmbientIntensityLocation;
};

Voici le premier exemple de l'utilisation de la classe Technique. LightingTechnique est une classe dérivée qui implémente la lumière, en utilisant les fonctionnalités communes de compilation et liaison fournies par la classe de base. La fonction Init() doit être appelée après que l'objet a été créé. Il appelle simplement Technique::AddShader() et Technique::Finalize() pour générer le programme GLSL.

Télécharger Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
#version 330

in vec2 TexCoord0;

out vec4 FragColor;

struct DirectionalLight
{
    vec3 Color;
    float AmbientIntensity;
};

uniform DirectionalLight gDirectionalLight;
uniform sampler2D gSampler;

void main()
{
    FragColor = texture2D(gSampler, TexCoord0.xy) *
            vec4(gDirectionalLight.Color, 1.0f) *
            gDirectionalLight.AmbientIntensity;
}

Le vertex shader reste inchangé dans ce tutoriel. Il continue de passer la position (après l'avoir multipliée par la matrice WVP) et les coordonnées de texture. Toute la nouvelle logique se situe dans le fragment shader. L'ajout ici est l'utilisation du mot-clef « struct » afin de définir la lumière directionnelle. Comme nous pouvons le voir, ce mot-clef est utilisé pratiquement de la même manière qu'en C/C++. La structure est identique à celle que nous avons dans le code de l'application et nous devons la garder ainsi afin que l'application et le shader puissent communiquer.

Il y a maintenant une nouvelle variable uniforme de type DirectionalLight, dont l'application a besoin pour la mise à jour. Cette variable est utilisée dans le calcul de la couleur finale du pixel. Comme avant, nous échantillonnons la texture pour récupérer la couleur de base. Nous la multiplions ensuite, par la couleur et l'intensité ambiante, selon la formule ci-dessus. Cela termine le calcul de la lumière ambiante.

Télécharger Sélectionnez
44.
45.
46.
47.
m_WVPLocation = GetUniformLocation("gWVP");
m_samplerLocation = GetUniformLocation("gSampler");
m_dirLightColorLocation = GetUniformLocation("gDirectionalLight.Color");
m_dirLightAmbientIntensityLocation = GetUniformLocation("gDirectionalLight.AmbientIntensity");

Afin d'accéder à la variable uniforme DirectionalLight à partir de l'application, nous devons récupérer la position de ses deux champs, indépendamment. La classe LightingTechnique garde quatre Gluint pour les positions des variables uniformes dans le vertex shader et le fragment shader. La matrice WVP et la position de l'échantillonneur sont récupérées d'une manière familière. La couleur et l'intensité ambiante sont récupérées de la manière que nous voyons : en spécifiant le nom de la variable uniforme, suivi d'un point puis du nom du champ dans la structure elle-même. La définition de la valeur de ces variables est faite de la même manière que pour n'importe quelle autre variable. La classe LightingTechnique fournit deux méthodes pour définir la lumière directionnelle et la matrice WVP. La classe Tutorial17 les appelle avant chaque dessin, pour mettre les valeurs à jour.

Ce tutoriel nous permet de jouer avec l'intensité ambiante, en utilisant les touches « a » et « s », qui l'augmentent et la réduisent, respectivement. Regardez la fonction KeyboardCB() dans la classe Tutorial17 pour voir comment c'est fait.

III. Sources▲

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources

IV. Remerciements▲

Merci à Etay Meiri de nous permettre de traduire son tutoriel.

Merci à LittleWhite pour ses corrections et à ClaudeLELOUP pour sa relecture.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+ inPartager

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 Etay Meiri. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -
  • Nous contacter
  • Participez
  • Hébergement
  • Informations légales

Copyright © 2000-2016 - www.developpez.com


Tutoriel 18 - Lumière diffuse

Table des matières Plier Déplier

  • I. Contexte
  • II. Explication du code
  • III. Point mathématique
  • IV. Sources
  • V. Remerciements

Ce tutoriel est le second de la série sur l'éclairage.

La principale différence entre la lumière ambiante et la lumière diffuse réside dans le fait que la lumière diffuse dépend de la direction des rayons de lumière, tandis que la lumière ambiante l'ignore complètement. Quand seule la lumière ambiante est présente, la scène entière est uniformément éclairée. La lumière diffuse fait que les parties des objets qui font face à la lumière sont plus claires que les parties qui lui tournent le dos.

La lumière diffuse ajoute aussi une astuce qui fait que l'angle dans lequel la lumière atteint la surface détermine la clarté de cette surface. Ce concept est illustré dans l'image suivante :

Image non disponible

Supposons que la force des deux rayons est identique et que leur seule différence est leur direction. Le modèle de lumière diffuse dit que la surface à gauche sera plus claire que la surface à droite, car la surface à droite est atteinte avec un angle plus aigu que la surface de gauche. En fait, la surface de gauche sera la plus claire possible, car la lumière l'atteint avec un angle de 90 degrés.

Le modèle de lumière diffuse est actuellement basé sur la « Lambert's cosine law », qui dit que l'intensité de la lumière reflétée sur une surface est directement proportionnelle au cosinus de l'angle entre la direction de l'observateur et la normale à la surface. Notons que nous avons un peu changé cela, en utilisant la direction de la source lumineuse au lieu de la direction de l'observateur (que nous utiliserons dans la lumière spéculaire).

Pour calculer l'intensité de la lumière dans le modèle de lumière diffuse, nous allons simplement utiliser le cosinus de l'angle entre la lumière et la normale à la surface (alors que la loi de Lambert se réfère au concept plus général de « directement proportionnelle »). Considérons l'image suivante :

Image non disponible

Nous voyons quatre rayons lumineux qui atteignent la surface avec des angles différents. La normale à la surface est la flèche verte qui pointe hors de la surface. Le rayon de lumière A est celui avec la plus grande force. L'angle entre A et la normale est zéro et le cosinus de zéro vaut un. Cela signifie qu'après la multiplication de l'intensité de la lumière (les trois canaux de zéro à un) par la couleur de la surface, nous multiplions le résultat par un. Nous ne pouvons pas avoir plus que cela avec la lumière diffuse. Le rayon B atteint la surface avec un angle entre 0 et 90. Cela signifie que l'angle entre B et la normale est lui aussi entre 0 et 90, et le cosinus de cet angle vaut entre zéro et un. Nous allons pondérer le résultat de la multiplication du dessus par le cosinus de cet angle, ce qui signifie que l'intensité de la lumière sera définitivement plus faible que celle du rayon A.

Les choses deviennent différentes avec les rayons C et D. C atteint la surface directement depuis le côté, avec un angle de zéro. L'angle entre C et la normale est exactement de 90 degrés, dont le cosinus vaut zéro. Le résultat est donc que C n'a aucun effet sur l'éclairage de la surface. L'angle entre D et la normale est obtus, ce qui signifie que son cosinus a une valeur négative. Le résultat final est le même que pour C : aucun effet sur l'éclairage de la surface.

À partir de cette discussion, on peut tirer une conclusion importante : afin d'avoir un effet sur la clarté de la surface, la lumière doit atteindre la surface de sorte que l'angle entre elle et la normale à la surface soit plus grand que zéro et strictement inférieur à 90 degrés.

Nous voyons que la normale à la surface joue un rôle important dans le calcul de la lumière diffuse. Les exemples ci-dessus étaient très simples : la surface était une simple ligne et il n'y avait qu'une seule normale à considérer. Dans le monde réel, nous avons des objets qui sont composés de multiples polygones et la normale à chaque polygone est différente de celle du polygone à côté. Voici un exemple :

Image non disponible

Comme la normale est la même tout le long de la surface du polygone, elle est suffisante pour calculer la lumière diffuse dans le vertex shader. Les trois sommets du triangle auraient la même couleur, qui serait celle du triangle complet. Cependant, cela ne serait pas très joli. Nous aurons un ensemble de polygones où chacun aura une couleur particulière, légèrement différente de celle du polygone d'à côté, et nous verrons les jointures des couleurs au niveau des arêtes. Cela peut assurément être amélioré.

L'astuce est d'utiliser un concept connu sous le nom de « normale par sommet ». Une normale par sommet est la moyenne des normales de tous les triangles qui partagent le sommet. Au lieu de demander au vertex shader de calculer la lumière diffuse, nous lui passons juste la normale du sommet comme attribut, qui est ensuite transmise au fragment shader, et c'est tout. Le rasteriser va récupérer trois normales différentes et va devoir effectuer une interpolation entre elles. Le fragment shader va être appelé pour chaque pixel avec la normale spécifique à ce pixel. Nous pouvons dès lors calculer la lumière diffuse au niveau du pixel, en utilisant cette normale spécifique. Le résultat va être un effet de lumière, qui va agréablement changer au travers de la face du triangle et entre les triangles voisins. Cette technique est connue sous le nom de « Ombrage Phong ». Voici à quoi ressembleront les normales des sommets après l'interpolation :

Image non disponible

Vous pouvez trouver que le modèle de pyramide, que nous avons utilisé lors des quelques tutoriels précédents, a un aspect étrange avec ces normales par sommet et décider de rester en normales originales. C'est compréhensible. Cependant, comme les modèles vont devenir de plus en plus complexes (et nous verrons cela dans le futur) et que leurs surfaces deviendront plus douces, je pense que vous trouverez les normales par sommet plus appropriées.

La seule chose restante dont nous devons nous occuper est l'espace de coordonnées dans lequel les calculs de lumière diffuse vont prendre place. Les sommets et leurs normales sont exprimés dans leur système local de coordonnées, puis sont transformés dans le vertex shader vers l'espace de découpe grâce à la matrice WVP que nous donnons au shader. Cependant, définir la direction de la lumière dans l'espace monde est l'approche la plus logique. Après tout, la direction de la lumière est le résultat d'une source lumineuse qui est positionnée quelque part dans le monde (même le soleil est situé dans le « monde », bien qu'à de nombreux kilomètres de distance) et répand sa lumière dans une direction particulière. Par conséquent, nous devons transformer les normales dans l'espace monde avant le calcul.

Télécharger Sélectionnez
25.
26.
27.
28.
29.
30.
31.
struct DirectionalLight
{
    Vector3f Color;
    float AmbientIntensity;
    Vector3f Direction;
    float DiffuseIntensity;
};

C'est la nouvelle structure DirectionalLight. Il y a deux nouveaux membres ici : la direction est un vecteur à trois dimensions défini dans l'espace monde et l'intensité est un nombre flottant (qui sera utilisé de la même manière que l'intensité ambiante).

Télécharger Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
#version 330

layout (location = 0) in vec3 Position;
layout (location = 1) in vec2 TexCoord;
layout (location = 2) in vec3 Normal;

uniform mat4 gWVP;
uniform mat4 gWorld;

out vec2 TexCoord0;
out vec3 Normal0;

void main()
{
    gl_Position = gWVP * vec4(Position, 1.0);
    TexCoord0 = TexCoord;
    Normal0 = (gWorld * vec4(Normal, 0.0)).xyz;
}

Voici le vertex shader mis à jour. Nous avons un nouvel attribut de sommet, la normale, que l'application va devoir fournir. De plus la transformation vers l'espace monde possède sa propre variable uniforme et l'application devra là aussi la fournir, en plus de la matrice WVP. Le vertex shader transforme la normale dans l'espace monde en utilisant la matrice monde et la passe au fragment shader. Notons comment la normale à trois dimensions est étendue à un vecteur quatre dimensions, multipliée par la matrice monde à quatre dimensions, puis réduite à nouveau à trois dimensions en utilisant la notation (…).xyz. Cette capacité du langage GLSL est appelée « réarrangement » et permet une grande flexibilité dans la manipulation des vecteurs. Par exemple, si nous avons un vecteur trois dimensions v(1,2,3), nous pouvons écrire : vec4 n = v.zzyy, ainsi le vecteur n contiendra (3,3,2,2). Rappelons-nous que lorsque nous étendons la normale de trois à quatre dimensions, nous devons placer zéro comme quatrième composante. Cela neutralise l'effet de la translation de la matrice monde (la quatrième colonne). La raison est que les vecteurs ne peuvent pas être déplacés comme des points. Ils peuvent uniquement être mis à l'échelle ou orientés.

Télécharger Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
#version 330

in vec2 TexCoord0;
in vec3 Normal0;

out vec4 FragColor;

struct DirectionalLight
{
    vec3 Color;
    float AmbientIntensity;
    float DiffuseIntensity;
    vec3 Direction;
};

Voici le début du fragment shader. Il reçoit maintenant la normale interpolée, qui a été transformée dans l'espace monde par le vertex shader. La structure DirectionalLight a été modifiée pour refléter celle dans le code C++ et contient les nouveaux attributs de la lumière.

Télécharger Sélectionnez
19.
20.
21.
22.
void main()
{
    vec4 AmbientColor = vec4(gDirectionalLight.Color, 1.0f) *
            gDirectionalLight.AmbientIntensity;

Il n'y a aucun changement dans le calcul du facteur de couleur ambiante. Nous le calculons et le stockons ici pour pouvoir l'utiliser dans la formule finale plus bas.

Sélectionnez
float DiffuseFactor = dot(normalize(Normal0), -gDirectionalLight.Direction);

Voici le cœur du calcul de lumière diffuse. Nous calculons le cosinus de l'angle entre le vecteur lumière et la normale en faisant un produit scalaire entre eux. Trois choses à noter ici :

  1. La normale venue du vertex shader est normalisée avant utilisation. On le fait parce que l'interpolation, au travers de laquelle le vecteur est passé, peut avoir modifié sa longueur et il n'est donc plus un vecteur unitaire ;
  2. La direction de la lumière a été inversée. Lorsqu'on y réfléchit un instant, nous voyons que la lumière qui atteint la surface à angle droit est en fait à 180 degrés de la normale de la surface (qui pointe simplement vers la source lumineuse). En inversant la direction de la lumière dans ce cas, nous obtenons un vecteur qui est égal à la normale. Par conséquent l'angle entre eux vaut zéro, ce que nous souhaitons ;
  3. le vecteur lumière n'est pas normalisé. Ce serait un gâchis de ressources GPU que de normaliser le même vecteur encore et encore, pour chaque pixel. À la place, nous nous assurons de normaliser le vecteur que l'application passe avant que le dessin ne soit fait.
Sélectionnez
vec4 DiffuseColor;

    if (DiffuseFactor > 0) {
        DiffuseColor = vec4(gDirectionalLight.Color, 1.0f) * gDirectionalLight.DiffuseIntensity * DiffuseFactor;
    }
    else {
        DiffuseColor = vec4(0, 0, 0, 0);
    }

Ici nous calculons le terme de diffusion, qui dépend de la couleur de la lumière, l'intensité de diffusion et la direction de la lumière. Si le facteur de diffusion est inférieur ou égal à zéro, cela signifie que la lumière atteint la surface avec un angle obtus (soit « de côté », soit « de derrière »). Dans ce cas la lumière diffuse n'a pas d'effet et le vecteur DiffuseColor est initialisé à zéro. Si le facteur est supérieur à zéro, nous calculons la couleur diffuse en multipliant la couleur de base de la lumière par l'intensité de diffusion, puis pondérons le résultat par le facteur de diffusion. Si l'angle entre la lumière et la normale est zéro, le facteur de diffusion vaudra un, ce qui fournit la force maximale de lumière.

Sélectionnez
    FragColor = texture2D(gSampler, TexCoord0.xy) * (AmbientColor + DiffuseColor);
}

Voici le calcul final de l'éclairage. Nous ajoutons les termes ambiant et diffusion, puis multiplions le résultat par la couleur échantillonnée à partir de la texture. Maintenant, nous voyons que même si la lumière diffuse n'a pas d'effet sur la surface (à cause de sa direction), la lumière ambiante peut toujours l'éclairer, si elle existe.

Télécharger Sélectionnez
144.
145.
146.
147.
148.
149.
150.
151.
152.
void LightingTechnique::SetDirectionalLight(const DirectionalLight& Light)
{
    glUniform3f(m_dirLightLocation.Color, Light.Color.x, Light.Color.y, Light.Color.z);
    glUniform1f(m_dirLightLocation.AmbientIntensity, Light.AmbientIntensity);
    Vector3f Direction = Light.Direction;
    Direction.Normalize();
    glUniform3f(m_dirLightLocation.Direction, Direction.x, Direction.y, Direction.z);
    glUniform1f(m_dirLightLocation.DiffuseIntensity, Light.DiffuseIntensity);
}

Cette fonction définit les paramètres de la lumière directionnelle dans le shader. Elle a été étendue pour couvrir le vecteur direction et l'intensité de diffusion. Notons que le vecteur direction est normalisé avant d'être défini. La classe LightingTechnique récupère aussi la position des variables uniformes dans le shader, pour la direction et pour l'intensité de diffusion, ainsi que pour la matrice monde. Il y a aussi une fonction pour définir la matrice de transformation monde. Toutes ces choses sont maintenant de jolies fonctions et le code n'est pas retranscrit ici. Visionnez les sources pour plus de détails.

Télécharger Sélectionnez
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
struct Vertex
{
    Vector3f m_pos;
    Vector2f m_tex;
    Vector3f m_normal;

    Vertex() {}

    Vertex(Vector3f pos, Vector2f tex)
    {
        m_pos = pos;
        m_tex = tex;
        m_normal = Vector3f(0.0f, 0.0f, 0.0f);
    }
};

La structure Vertex a été mise à jour et inclut maintenant la normale. Elle est initialisée automatiquement à zéro par le constructeur et nous avons une fonction dédiée qui parcourt tous les sommets et calcule les normales.

Télécharger Sélectionnez
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
void CalcNormals(const unsigned int* pIndices, unsigned int IndexCount, Vertex* pVertices, unsigned int VertexCount)
{
    for (unsigned int i = 0 ; i < IndexCount ; i += 3) {
        unsigned int Index0 = pIndices[i];
        unsigned int Index1 = pIndices[i + 1];
        unsigned int Index2 = pIndices[i + 2];
        Vector3f v1 = pVertices[Index1].m_pos - pVertices[Index0].m_pos;
        Vector3f v2 = pVertices[Index2].m_pos - pVertices[Index0].m_pos;
        Vector3f Normal = v1.Cross(v2);
        Normal.Normalize();

        pVertices[Index0].m_normal += Normal;
        pVertices[Index1].m_normal += Normal;
        pVertices[Index2].m_normal += Normal;
    }

    for (unsigned int i = 0 ; i < VertexCount ; i++) {
        pVertices[i].m_normal.Normalize();
    }
}

Cette fonction prend un tableau de sommets et un tableau d'indices, parcourt chaque triangle en fonction des indices et calcule sa normale. Dans la première boucle, nous accumulons simplement les normales des trois sommets du triangle. Pour chaque triangle, la normale est calculée comme le produit vectoriel entre deux arêtes venant du premier sommet. Avant d'accumuler la normale dans le sommet, nous nous assurons de la normaliser. La raison est que le résultat du produit vectoriel n'est pas forcément un vecteur unitaire. Dans la seconde boucle, nous parcourons le tableau de sommets directement (nous n'avons plus à nous préoccuper des indices) et normalisons la normale de chaque sommet. Cette opération est l'équivalent de faire la moyenne de la somme de toutes les normales accumulées et nous laisse avec une normale par sommet qui est de longueur unitaire. Cette fonction est appelée avant la création du tampon de sommets, de manière à avoir dans ce tampon les normales calculées, avec les autres attributs de sommets.

Télécharger Sélectionnez
131.
132.
133.
134.
135.
136.
137.
138.
    const Matrix4f& WorldTransformation = p.GetWorldTrans();
    m_pEffect->SetWorldMatrix(WorldTransformation);
    ...
    glEnableVertexAttribArray(2);
    ...
    glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)20);
    ...
    glDisableVertexAttribArray(2);

Voici les changements principaux de la boucle de rendu. La classe Pipeline a une nouvelle fonction qui fournit la matrice de transformation monde (en plus de la matrice WVP). La matrice monde est calculée comme la multiplication de la matrice de mise à l'échelle par la matrice de rotation et finalement par la matrice de translation. Nous activons et désactivons le troisième attribut de sommet, et spécifions le décalage de la normale à l'intérieur de chaque sommet du tampon de sommets. Le décalage vaut 20, car la normale est précédée de la position (12 octets) et des coordonnées de texture (8 octets).

Pour compléter la démonstration que nous voyons dans l'image de ce tutoriel, nous devons aussi spécifier l'intensité de diffusion et la direction de la lumière. Cela est fait dans le constructeur de la classe Tutorial18. L'intensité de diffusion est définie à 0.8 et la direction de la lumière va de gauche à droite. L'intensité de l'ambiante a été réduite à zéro pour amplifier l'effet de la lumière diffuse. Nous pouvons jouer avec les touches « z » et « x » pour contrôler l'intensité de diffusion (comme « a » et « s » du tutoriel précédent modifient l'intensité de l'ambiante).

Image non disponible
Résultat

III. Point mathématique▲

De nombreuses sources sur Internet vous diront que vous devez transposer l'inverse de la matrice monde pour transformer le vecteur normal. C'est correct, cependant nous n'avons généralement pas besoin d'aller si loin. Nos matrices monde sont toujours orthogonales (leurs vecteurs sont toujours orthogonaux). Comme l'inverse d'une matrice orthogonale est égale à sa transposée, la transposée de l'inverse est en fait la transposée de la transposée, et nous arrivons donc avec notre matrice originale. Aussi longtemps que nous évitons de faire des distorsions (pondérer un axe différemment des autres) nous pouvons continuer avec l'approche présentée ci-dessus.

IV. Sources▲

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources

V. Remerciements▲

Merci à Etay Meiri de nous permettre de traduire son tutoriel.

Merci à LittleWhite pour ses corrections et à ClaudeLELOUP pour sa relecture.

Navigation▲

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+ inPartager

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 Etay Meiri. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -
  • Nous contacter
  • Participez
  • Hébergement
  • Informations légales

Copyright © 2000-2016 - www.developpez.com


Tutoriel 19 - Lumière spéculaire

Table des matières Plier Déplier

  • I. Contexte
  • II. Explication du code
  • III. Sources
  • IV. Remerciements

Ce tutoriel est le troisième de la série sur l'éclairage.

Lorsque nous avons calculé l'éclairage ambiant, le seul facteur était l'intensité de la lumière. Ensuite, nous avons ajouté la lumière diffuse, qui prenait en compte la direction de la lumière dans l'équation. La lumière spéculaire inclut ces facteurs et ajoute un nouvel élément au tout : la position de l'observateur.

L'idée est que lorsque la lumière atteint une surface avec un angle donné, elle est aussi réfléchie avec le même angle (du côté opposé par rapport à la normale). Si l'observateur est situé quelque part le long du rayon reflété, il reçoit une plus grande quantité de lumière qu'un observateur placé ailleurs.

Le résultat final de la lumière spéculaire rendra les objets plus clairs sous certains angles et cette clarté diminuera si l'observateur se déplace. L'exemple parfait de la lumière spéculaire dans le monde réel est un objet métallique. Ce type d'objets peut parfois être tellement clair qu'au lieu de voir la couleur naturelle de l'objet on voit une tache de lumière blanche directement reflétée vers vous. Cependant, ce type de caractéristique, naturelle pour les métaux, est absente pour de nombreux autres matériaux (par exemple : le bois). De nombreux objets ne brillent tout simplement pas, quel que soit l'endroit d'où vient la lumière et quelle que soit la position de l'observateur. La conclusion est que le facteur spéculaire dépend plus de l'objet que de la lumière elle-même.

Voyons comment nous pouvons amener la position de l'observateur dans le calcul de la lumière spéculaire. Regardons l'image suivante :

Image non disponible

Il y a cinq choses auxquelles il faut prêter attention :

  • « I » est la lumière incidente qui atteint la surface (et génère la lumière diffuse) ;
  • « N » est la normale à la surface ;
  • « R » est le rayon de lumière qui est réfléchi par la surface. Il est symétrique à « I » par rapport à N, mais sa direction est inversée (il pointe vers le « haut » et pas vers le « bas »).
  • « V » est le vecteur qui va du point d'intersection entre « I » et la surface jusqu'à l'« œil » (qui représente l'observateur).
  • « α » est l'angle créé par les vecteurs « R » et « V ».

Nous allons modéliser le phénomène de lumière spéculaire en utilisant l'angle « α ». L'idée derrière la lumière spéculaire est que la force du rayon réfléchi est à son maximum le long du vecteur « R ». Dans ce cas, « V » est identique à « R » et l'angle vaut zéro. Quand l'observateur commence à s'éloigner de « R », l'angle grandit. Nous voulons que l'effet de la lumière diminue quand l'angle augmente. À présent, nous pouvons deviner que nous allons utiliser le produit scalaire, une nouvelle fois, afin de calculer le cosinus de « α ». Cela servira de facteur spéculaire dans la formule d'éclairage. Quand « α » vaut zéro, son cosinus vaut un, ce qui est le facteur maximum que nous puissions obtenir. Quand « α » diminue, son cosinus devient plus petit jusqu'à ce que « α » atteigne 90 degrés, où le cosinus vaut zéro, et où il n'y a pas d'effet spéculaire. Quand « α » vaut plus de 90 degrés, le cosinus devient négatif et il n'y a pas non plus d'effet spéculaire. Cela signifie que l'observateur n'est pas du tout dans le chemin du rayon réfléchi.

Pour calculer « α », nous allons avoir besoin de « R » et de « V ». « V » peut être calculé en soustrayant la position du point d'impact de la lumière, dans l'espace monde, à la position de l'observateur (lui aussi dans l'espace monde). Comme notre caméra se situe déjà dans l'espace monde, nous n'avons qu'à passer sa position au shader. Comme l'image ci-dessus est simplifiée, il n'y a qu'un seul point que la lumière atteint. En réalité, le triangle complet est éclairé (si tant est qu'il fait face à la lumière). Donc, nous calculons l'effet spéculaire pour chaque pixel (comme nous le faisons déjà avec la lumière diffuse) et pour cela, nous avons besoin de la position de chaque pixel dans l'espace monde. Là aussi, c'est simple : nous transformons les sommets dans l'espace monde, puis nous laissons le rasterizer interpoler la position monde du pixel et nous fournir le résultat dans le fragment shader. En fait, c'est le même travail que celui que nous avons effectué sur la normale dans le tutoriel précédent.

La seule chose qu'il nous reste à faire est de calculer le rayon réfléchi « R », en utilisant le vecteur « I » (fourni au shader par l'application). Regardons l'image suivante :

Image non disponible

Souvenons-nous qu'un vecteur n'a pas vraiment de point de départ et que tous les vecteurs ayant la même direction et la même norme sont égaux. Par conséquent, le vecteur « I » a été copié « en dessous » de la surface et la copie est identique à l'original. L'objectif est de calculer le vecteur « R ». Selon les règles de l'addition de vecteurs, « R » est égal à « I » + « V ». « I » est déjà connu donc nous n'avons qu'à calculer « V ». Notons que l'opposée à la normale « V » apparaît aussi comme « -N » et l'utilisation d'un produit scalaire entre « I » et « -N » nous permet de trouver la norme du vecteur créé lorsqu'on projette « I » sur « -N ». Cette norme est exactement la moitié de la norme de « V ». Comme « V » a la même direction que « N », nous calculons « V » en multipliant « N » (dont la norme vaut 1.0) par deux fois la valeur de cette norme. En résumé :

R=I+V R=I+V
V=2×N×(−N.I)V=2×N×(−N.I)
⇒R=I+2×N×(−N.I)=I−2×N×(N.I)⇒R=I+2×N×(−N.I)=I−2×N×(N.I)

Maintenant que vous comprenez ces mathématiques, il est temps de vous révéler un petit secret : GLSL fournit une fonction interne appelée « reflect » qui fait exactement ce calcul. Nous verrons plus bas comment l'utiliser dans un shader.

Finalisons la formule de la lumière spéculaire :

⎛⎝⎜⎜RspéculaireGspéculaireBspéculaire⎞⎠⎟⎟=⎛⎝⎜⎜RlumièreGlumièreBlumière⎞⎠⎟⎟×⎛⎝⎜RsurfaceGsurfaceBsurface⎞⎠⎟×M×(R.V)P(R sp é culaireG sp é culaireB sp é culaire)=(R lumi è reG lumi è reB lumi è re)×(R surfaceG surfaceB surface)×M×(R.V)P

Nous commençons en multipliant la couleur de la lumière par la couleur de la surface. C'est la même chose qu'avec les lumières ambiante et diffuse. Le résultat est multiplié par l'intensité spéculaire du matériau (« M »). Un matériau qui n'a pas de propriété spéculaire (par exemple : du bois) aura une intensité spéculaire de zéro, ce qui mettra à zéro le résultat de l'équation. Des objets plus brillants, comme le métal, peuvent avoir des niveaux de plus en plus élevés d'intensité spéculaire. Après cela, nous multiplions ce résultat par le cosinus de l'angle entre le rayon réfléchi et le vecteur vers l'observateur. Notons que cette dernière partie est élevée à la puissance « P ». « P » est appelée « puissance spéculaire » ou « facteur de brillance ». Son travail est d'intensifier et d'affiner les bords dans les zones où la lumière spéculaire est présente. L'image suivante montre l'effet de la puissance spéculaire quand elle est mise à 1 :

Image non disponible

Alors que la suivante montre un exposant spéculaire de 32 :

Image non disponible

La puissance spéculaire est aussi considérée comme un attribut du matériau donc différents objets peuvent avoir différentes valeurs de puissance spéculaire.

II. Explication du code▲

Télécharger Sélectionnez
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
class LightingTechnique : public Technique
{
public:
...
    void SetEyeWorldPos(const Vector3f& EyeWorldPos);
    void SetMatSpecularIntensity(float Intensity);
    void SetMatSpecularPower(float Power);

private:
...
    GLuint m_eyeWorldPosLocation;
    GLuint m_matSpecularIntensityLocation;
    GLuint m_matSpecularPowerLocation;

Il y a trois nouveaux attributs dans la classe LightingTechnique : la position de l'observateur, l'intensité spéculaire et la puissance spéculaire du matériau. Les trois sont indépendantes de la lumière elle-même. La raison est que lorsque la même lumière atteint deux matériaux différents (par exemple : du métal et du bois), chacun brille d'une manière différente. L'utilisation de ces deux attributs pour le matériau impose certaines limites. Tous les triangles faisant partie du même appel à la fonction de dessin auront la même valeur pour ces attributs. Cela peut être gênant quand les triangles représentent des parties différentes du modèle, avec différentes propriétés de matériau. Quand nous atteindrons les tutoriels de chargement de maillage, nous verrons que nous pouvons générer différentes valeurs de spéculaire dans les applications de modelage et les intégrer au tampon de sommets (au lieu de paramètres de shader). Cela nous permettra de dessiner des triangles avec différents éclairages spéculaires dans le même appel. Pour l'instant, l'approche simple que nous en avons faite suffira (comme exercice, vous pouvez essayer d'ajouter l'intensité spéculaire et la puissance spéculaire au tampon de sommets et y accéder dans le shader).

Télécharger Sélectionnez
12.
13.
14.
15.
16.
17.
18.
19.
20.
out vec3 WorldPos0;

void main()
{
    gl_Position = gWVP * vec4(Position, 1.0);
    TexCoord0 = TexCoord;
    Normal0 = (gWorld * vec4(Normal, 0.0)).xyz;
    WorldPos0 = (gWorld * vec4(Position, 1.0)).xyz;
}

Le vertex shader ci-dessus intègre une seule nouvelle ligne (la dernière). La matrice monde (que nous avons ajoutée dans le tutoriel précédent pour transformer la normale) est maintenant utilisée pour passer la position monde du sommet au fragment shader. Nous voyons une technique intéressante, qui est de transformer la même position du sommet (qui est fournie dans l'espace local) en utilisant deux matrices différentes et de passer les résultats indépendamment au fragment shader. Le résultat de la transformation complète (matrice de transformation monde-vue-projection) va dans la variable du GLSL « gl_Position », le GPU s'occupe de la transformer dans l'espace-écran et l'utilise dans le procédé de rastérisation. Le résultat de la transformation « partielle » (uniquement dans l'espace monde) va dans un attribut défini par l'utilisateur, qui est simplement interpolé durant la rastérisation, afin que chaque pixel pour lequel le fragment shader est invoqué reçoive sa propre position dans l'espace monde. Cette pratique est très utile et est souvent employée.

Télécharger Sélectionnez
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
in vec3 WorldPos0;
.
.
.
uniform vec3 gEyeWorldPos;
uniform float gMatSpecularIntensity;
uniform float gSpecularPower;

void main()
{
    vec4 AmbientColor = vec4(gDirectionalLight.Color, 1.0f) * gDirectionalLight.AmbientIntensity;
    vec3 LightDirection = -gDirectionalLight.Direction;
    vec3 Normal = normalize(Normal0);

    float DiffuseFactor = dot(Normal, LightDirection);

    vec4 DiffuseColor = vec4(0, 0, 0, 0);
    vec4 SpecularColor = vec4(0, 0, 0, 0);

    if (DiffuseFactor > 0) {
        DiffuseColor = vec4(gDirectionalLight.Color, 1.0f) *
            gDirectionalLight.DiffuseIntensity *
            DiffuseFactor;

        vec3 VertexToEye = normalize(gEyeWorldPos - WorldPos0);
        vec3 LightReflect = normalize(reflect(gDirectionalLight.Direction, Normal));
        float SpecularFactor = dot(VertexToEye, LightReflect);
        SpecularFactor = pow(SpecularFactor, gSpecularPower);
        if (SpecularFactor > 0) {
            SpecularColor = vec4(gDirectionalLight.Color, 1.0f) * gMatSpecularIntensity * SpecularFactor;
        }
    }

    FragColor = texture2D(gSampler, TexCoord0.xy) * (AmbientColor + DiffuseColor + SpecularColor);
}

Il y a plusieurs changements dans le vertex shader. On y trouve maintenant trois nouvelles variables uniformes, qui stockent les attributs requis pour le calcul de la lumière spéculaire (position de l'observateur, intensité et puissance spéculaire). La couleur ambiante est calculée de la même manière que dans les deux tutoriels précédents. Puis les couleurs diffuse et spéculaire sont créées et initialisées à zéro. Elles ont toutes les deux une valeur différente de zéro uniquement lorsque l'angle entre la lumière et la surface vaut moins de 90 degrés. La vérification est faite en utilisant le facteur de diffusion (de la même manière que dans le tutoriel sur la lumière diffuse).

L'étape suivante est le calcul du vecteur allant du sommet en position monde à la position de l'observateur (elle aussi dans l'espace monde). Nous faisons ce calcul en soustrayant la position monde du sommet à la position de l'observateur, qui est une variable uniforme et est identique pour tous les pixels. Ce vecteur est normalisé pour le rendre utilisable dans le produit scalaire. Après cela, le vecteur lumière réfléchi est calculé en utilisant la fonction interne « reflect » (nous pourrions aussi le calculer manuellement, en suivant la description plus haut). Cette fonction accepte deux paramètres : le vecteur lumière et la normale à la surface. L'important ici est d'utiliser le vecteur lumière original, celui qui va vers la surface, pas celui qui a été utilisé pour le calcul du facteur de diffusion. C'est évident lorsqu'on regarde le diagramme ci-dessus. Ensuite, nous calculons le facteur spéculaire, à partir du cosinus de l'angle entre le rayon de lumière réfléchi et le vecteur du sommet à l'observateur (là encore avec un produit scalaire).

L'effet spéculaire est visible uniquement si cet angle est inférieur à 90 degrés. Par conséquent, nous vérifions que le résultat du dernier produit scalaire est supérieur à zéro. La couleur spéculaire finale est calculée en multipliant la couleur de la lumière par l'intensité spéculaire du matériau et le facteur spéculaire. Nous ajoutons les couleurs ambiante, diffuse et spéculaire pour calculer la couleur complète de la lumière. Elle est multipliée par la couleur échantillonnée depuis la texture et fournit la couleur finale du pixel.

Télécharger Sélectionnez
134.
135.
136.
m_pEffect->SetEyeWorldPos(m_pGameCamera->GetPos());
m_pEffect->SetMatSpecularIntensity(1.0f);
m_pEffect->SetMatSpecularPower(32);

Utiliser la couleur spéculaire est très simple. Dans la boucle de rendu, nous récupérons la position de la caméra (qui est déjà définie dans l'espace monde) et la passons à la technique de lumière. Nous définissons aussi l'intensité spéculaire et sa puissance. Tout le reste est géré par le shader.

Jouez avec les différentes valeurs spéculaires et la direction de la lumière pour voir leur effet. Vous devrez probablement vous déplacer autour de l'objet pour vous placer dans une position où la lumière spéculaire est visible.

III. Sources▲

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources

IV. Remerciements▲

Merci à Etay Meiri de nous permettre de traduire son tutoriel.

Merci à LittleWhite pour ses corrections et à ced pour sa relecture.

V. Navigation▲

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+ inPartager

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 Etay Meiri. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite - Contacter par email
  • Nous contacter
  • Participez
  • Hébergement
  • Informations légales

Copyright © 2000-2016 - www.developpez.com


Tutoriel 20 - Lumières ponctuelles

Table des matières Plier Déplier

  • I. Contexte
  • II. Explication du code
  • III. Sources
  • IV. Remerciements

Quatrième chapitre sur l'éclairage, ce tutoriel étudie les sources de lumières ponctuelles.

Nous avons étudié les trois modèles de l'éclairage basique (ambiant, diffus, spéculaire), sous le couvert de la lumière directionnelle. La lumière directionnelle est un type de lumière caractérisé par un simple vecteur direction et l'absence d'origine. Par conséquent, son éclairage ne devient pas plus faible avec la distance (en fait, on ne peut pas définir la distance entre la source lumineuse et la cible éclairée). Nous allons maintenant voir le type de lumière appelé « lumière ponctuelle », qui a un point d'origine ainsi qu'un effet d'affaiblissement, qui augmente à mesure que les objets s'en éloignent. Un exemple classique de lumière ponctuelle est une ampoule. Nous ne pouvons pas bien nous rendre compte de l'effet d'affaiblissement lorsque l'ampoule est dans une pièce, mais, en la plaçant à l'extérieur, nous voyons bien comme sa puissance est limitée. Notons que la direction de la lumière, qui est une constante de scène pour une lumière directionnelle, devient dynamique pour une lumière ponctuelle. C'est parce qu'une lumière ponctuelle brille de la même manière dans toutes les directions, donc la direction doit être calculée pour chaque objet, en récupérant le vecteur entre l'objet et la source lumineuse. C'est pour cela que nous spécifions l'origine plutôt que la direction des lumières ponctuelles.

L'effet d'affaiblissement des lumières ponctuelles est généralement appelé « atténuation ». L'atténuation pour les lumières réelles est régie par la loi des carrés inverses, qui dit que l'intensité de la lumière est inversement proportionnelle au carré de la distance à la source de la lumière. Ce concept est décrit de manière mathématique par la formule suivante :

Ldistance=L1distance2 (L1−force de la lumière à la distance 1)L distance=L 1 distanc e 2 (L 1−force de la lumi è re à la distance 1)

Cette formule ne donne pas de bons résultats en graphisme 3D. Par exemple, comme la distance devient plus petite, l'intensité de la lumière s'approche de l'infini. En outre, le développeur n'a pas de contrôle sur le résultat, à part en définissant l'intensité initiale de la lumière. C'est trop limité. Par conséquent, nous ajoutons quelques facteurs à la formule ci-dessus pour la rendre plus flexible :

Ldistance=L1Attenuationconstante+Attenuationlinéaire×distance+Attenuationexp×distance2 L distance=L 1 Attenuatio n constante+Attenuatio n lin é aire×distance+Attenuatio n exp×distanc e 2

Nous avons ajouté trois facteurs d'atténuation de la lumière au dénominateur : un facteur constant, un facteur linéaire et un facteur exponentiel. La formule physiquement correcte est retrouvée en mettant les facteurs linéaire et constant à zéro et le facteur exponentiel à un. Nous pourrons trouver utile de mettre le facteur constant à un et les deux autres à des fractions plus petites. En mettant le facteur constant à un, nous garantissons que l'intensité de la lumière atteint son maximum (en fait, la valeur configurée dans le programme) à la distance zéro et décroît avec l'augmentation de la distance, car le dénominateur devient plus grand que un. En réglant correctement les facteurs, linéaire et exponentiel, nous atteindrons l'effet désiré de lumière qui s'affaiblit rapidement ou lentement, suivant la distance.

Résumons les étapes requises pour le calcul d'une lumière ponctuelle :

  1. Calculer le terme ambiant, de la même manière que pour une lumière directionnelle ;
  2. Calculer la direction de la lumière comme étant le vecteur allant du pixel (dans l'espace monde) au point d'origine de la source lumineuse. Nous pouvons maintenant calculer les termes de diffusion et de spéculaire, de la même manière que pour une lumière directionnelle, en utilisant cette fois la direction calculée ;
  3. Calculer la distance entre le pixel et le point d'origine de la source lumineuse et l'utiliser pour calculer la valeur totale d'atténuation ;
  4. Ajouter les trois termes et diviser cette somme par l'atténuation pour obtenir la couleur finale.

II. Explication du code▲

Télécharger Sélectionnez
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
struct BaseLight
{
    Vector3f Color;
    float AmbientIntensity;
    float DiffuseIntensity;
};
.
.
.
struct PointLight : public BaseLight
{
    Vector3f Position;

    struct
    {
        float Constant;
        float Linear;
        float Exp;
    } Attenuation;
}

En dépit de leurs différences, les sources lumineuses directionnelles et ponctuelles ont beaucoup en commun. Ces données communes ont été déplacées dans la structure BaseLight, dont les deux types de lumière dérivent maintenant. La structure de lumière directionnelle ajoute la direction tandis que la structure de lumière ponctuelle ajoute la position (dans l'espace monde) et les trois facteurs d'atténuation.

Télécharger Sélectionnez
81.
void SetPointLights(unsigned int NumLights, const PointLight* pLights);

En plus de montrer comment implémenter une lumière ponctuelle, ce tutoriel montre aussi comment utiliser plusieurs sources lumineuses. Nous considérons ici qu'il n'y a généralement qu'une seule lumière directionnelle (jouant le rôle de « soleil ») et plusieurs sources lumineuses ponctuelles possibles (des ampoules dans des pièces, des torches dans un donjon, etc.). Cette fonction prend en paramètre un tableau de structures PointLight ainsi que sa taille et met à jour le shader avec leurs valeurs.

Télécharger Sélectionnez
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
struct {
    GLuint Color;
    GLuint AmbientIntensity;
    GLuint DiffuseIntensity;
    GLuint Position;
    struct
    {
        GLuint Constant;
        GLuint Linear;
        GLuint Exp;
    } Atten;
} m_pointLightsLocation[MAX_POINT_LIGHTS];

Afin de supporter plusieurs lumières ponctuelles, le shader contient un tableau de structures identiques à la structure PointLight (en GLSL, cette fois). Il y a deux méthodes pour mettre à jour un tableau de structures dans les shaders :

  1. Nous pouvons récupérer la position de chaque membre de la structure, pour chaque élément du tableau (donc un tableau de cinq structures avec quatre champs amène à vingt positions de variables uniformes) et définir la valeur de chaque champ séparément ;
  2. Nous pouvons récupérer la position des champs du premier élément et utiliser une fonction OpenGL, qui définit les valeurs d'un tableau de variables, pour chaque type d'attribut spécifique à un champ. Par exemple, le premier champ est un flottant et le second est un entier, donc on peut définir toutes les valeurs du premier champ en donnant un tableau de flottants en un appel et définir le second champ avec un tableau d'entiers en un second appel.

La première méthode est la moins rentable en termes de nombre de positions de variables uniformes à stocker, mais est plus flexible à l'utilisation. Elle permet de mettre à jour n'importe quelle variable du tableau en accédant simplement à sa position et ne requiert pas la transformation de données qu'impose la seconde méthode.

La seconde méthode requiert moins de gestion de positions de variables uniformes ; cependant, si nous voulons mettre à jour plusieurs tableaux d'éléments à la fois et que l'utilisateur donne un tableau de structures (comme le fait la méthode SetPointLights()), nous allons devoir les transformer en une structure de tableaux, à cause du fait que les positions de variables uniformes représentent des tableaux de variables de même type. Lors de l'utilisation d'un tableau de structures, il y a un espace en mémoire entre les positions du même champ de deux structures consécutives dans le tableau, ce qui requiert de les rassembler dans un même tableau.

Dans ce tutoriel, nous allons utiliser la première méthode. Vous pouvez vous amuser avec les deux pour décider laquelle fonctionne le mieux dans votre cas.

La constante MAX_POINT_LIGHTS limite le nombre de lumières ponctuelles pouvant être utilisées et doit être synchronisée avec la valeur correspondante dans le shader. La valeur par défaut est 2. En augmentant le nombre de sources lumineuses dans votre application, vous pouvez rencontrer des problèmes de performances, qui empirent avec l'augmentation du nombre de lumières. Ce problème peut être contourné en utilisant une technique appelée « rendu différé » qui sera traitée plus tard.

Télécharger Sélectionnez
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
vec4 CalcLightInternal(BaseLight Light, vec3 LightDirection, vec3 Normal)
{
    vec4 AmbientColor = vec4(Light.Color, 1.0f) * Light.AmbientIntensity;
    float DiffuseFactor = dot(Normal, -LightDirection);

    vec4 DiffuseColor = vec4(0, 0, 0, 0);
    vec4 SpecularColor = vec4(0, 0, 0, 0);

    if (DiffuseFactor > 0) {
        DiffuseColor = vec4(Light.Color, 1.0f) * Light.DiffuseIntensity * DiffuseFactor;
        vec3 VertexToEye = normalize(gEyeWorldPos - WorldPos0);
        vec3 LightReflect = normalize(reflect(LightDirection, Normal));
        float SpecularFactor = dot(VertexToEye, LightReflect);
        SpecularFactor = pow(SpecularFactor, gSpecularPower);
        if (SpecularFactor > 0) {
            SpecularColor = vec4(Light.Color, 1.0f) *
            gMatSpecularIntensity * SpecularFactor;
        }
    }

    return (AmbientColor + DiffuseColor + SpecularColor);
}

Il n'est pas surprenant de remarquer que nous avons beaucoup de code en commun, entre les lumières directionnelles et les lumières ponctuelles. La plupart des algorithmes sont les mêmes. La différence réside dans le fait que nous devons ajouter le facteur d'atténuation uniquement pour la lumière ponctuelle. De plus, la direction de la lumière est fournie par l'application dans le cas de la lumière directionnelle alors qu'elle est calculée par pixel dans le cas de la lumière ponctuelle.

La fonction ci-dessus encapsule le code commun aux deux types de lumières. La structure BaseLight contient les intensités et la couleur. Le membre LightDirection est fourni séparément à cause de la raison décrite précédemment. La normale au sommet est aussi fournie, car nous la normalisons une fois en entrée du fragment shader puis l'utilisons plusieurs fois dans cette fonction.

Télécharger Sélectionnez
70.
71.
72.
73.
vec4 CalcDirectionalLight(vec3 Normal)
{
    return CalcLightInternal(gDirectionalLight.Base, gDirectionalLight.Direction, Normal);
}

Avec la mise en place de la fonction commune, la fonction de calcul de la lumière directionnelle devient une simple enveloppe pour celle-ci, renseignant la plupart des paramètres à partir de variables globales.

lighting.fs
Sélectionnez
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
vec4 CalcPointLight(int Index, vec3 Normal)
{
    vec3 LightDirection = WorldPos0 - gPointLights[Index].Position;
    float Distance = length(LightDirection);
    LightDirection = normalize(LightDirection);

    vec4 Color = CalcLightInternal(gPointLights[Index].Base, LightDirection, Normal);
    float Attenuation = gPointLights[Index].Atten.Constant +
                        gPointLights[Index].Atten.Linear * Distance +
                        gPointLights[Index].Atten.Exp * Distance * Distance;

    return Color / Attenuation;
}

Calculer une lumière ponctuelle est un peu plus complexe que calculer une lumière directionnelle. Cette fonction sera appelée pour chaque source ponctuelle donc elle prend l'indice de la source en paramètre et l'utilise comme index du tableau de lumières ponctuelles. Elle calcule le vecteur partant de la source (fournie dans l'espace monde par l'application) vers la position du pixel dans l'espace monde, fourni par le vertex shader. La distance de la source au pixel est calculée en utilisant la fonction interne length(). Maintenant que nous avons la distance, nous normalisons le vecteur de direction de la lumière. Rappelons-nous que CalcLightInternal() s'attend à ce qu'il soit normalisé et, dans le cas d'une lumière directionnelle, la classe LightingTechnique s'en occupe. Nous récupérons la couleur en sortie de la fonction CalcLightInternal() et utilisons la distance récupérée auparavant pour calculer l'atténuation. La couleur finale de la source lumineuse ponctuelle est calculée en divisant la couleur obtenue par l'atténuation.

Télécharger Sélectionnez
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
void main()
{
    vec3 Normal = normalize(Normal0);
    vec4 TotalLight = CalcDirectionalLight(Normal);

    for (int i = 0 ; i < gNumPointLights ; i++) {
        TotalLight += CalcPointLight(i, Normal);
    }

    FragColor = texture2D(gSampler, TexCoord0.xy) * TotalLight;
}

Maintenant que nous avons toute l'infrastructure en place, le fragment shader devient très simple. Il effectue la normalisation de la normale au sommet et accumule les résultats d'application des sources lumineuses. Le résultat est multiplié par la couleur échantillonnée et est utilisé comme couleur finale du pixel.

Télécharger Sélectionnez
279.
280.
281.
282.
283.
284.
285.
286.
287.
288.
289.
290.
291.
292.
void LightingTechnique::SetPointLights(unsigned int NumLights, const PointLight* pLights)
{
    glUniform1i(m_numPointLightsLocation, NumLights);

    for (unsigned int i = 0 ; i < NumLights ; i++) {
        glUniform3f(m_pointLightsLocation[i].Color, pLights[i].Color.x, pLights[i].Color.y, pLights[i].Color.z);
        glUniform1f(m_pointLightsLocation[i].AmbientIntensity, pLights[i].AmbientIntensity);
        glUniform1f(m_pointLightsLocation[i].DiffuseIntensity, pLights[i].DiffuseIntensity);
        glUniform3f(m_pointLightsLocation[i].Position, pLights[i].Position.x, pLights[i].Position.y, pLights[i].Position.z);
        glUniform1f(m_pointLightsLocation[i].Atten.Constant, pLights[i].Attenuation.Constant);
        glUniform1f(m_pointLightsLocation[i].Atten.Linear, pLights[i].Attenuation.Linear);
        glUniform1f(m_pointLightsLocation[i].Atten.Exp, pLights[i].Attenuation.Exp);
    }
}

Cette fonction met à jour les valeurs des lumières ponctuelles dans le shader, en itérant sur le tableau d'éléments et en passant une à une les valeurs de chaque élément. C'est la « méthode 1 » décrite précédemment.

La démonstration de ce tutoriel montre deux lumières ponctuelles se poursuivant sur le terrain. Les déplacements d'une des sources sont basés sur la fonction cosinus et les déplacements de l'autre le sont sur la fonction sinus. Le terrain est un simple quadrilatère formé par deux triangles. La normale est un vecteur pointant directement vers le haut.

III. Sources▲

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources

IV. Remerciements▲

Merci à Etay Meiri de nous permettre de traduire son tutoriel.

Merci à LittleWhite pour ses corrections et à ced pour sa relecture.

Navigation▲

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+ inPartager

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 Etay Meiri. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -
  • Nous contacter
  • Participez
  • Hébergement
  • Informations légales

Copyright © 2000-2016 - www.developpez.com


Tutoriel 21 - Projecteurs

Table des matières Plier Déplier

  • I. Contexte
  • II. Explication du code
  • III. Sources
  • IV. Remerciements

I. Contexte▲

Dernier tutoriel sur l'éclairage, nous y étudions les sources de lumière de type projecteur.

Le projecteur est la troisième et dernière source de lumière que nous allons étudier (pour au moins un petit moment…). Elle est plus complexe que la lumière directionnelle et la lumière ponctuelle et emprunte beaucoup aux deux. Le projecteur a une position d'origine et un effet d'atténuation lié à l'éloignement de la cible (comme les lumières ponctuelles), de plus sa lumière pointe dans une direction spécifique (comme les lumières directionnelles). La source lumineuse projecteur ajoute un attribut unique : elle se déverse uniquement à l'intérieur d'un cône limité, qui s'élargit avec l'éloignement de la lumière par rapport à son origine. Un bon exemple de projecteur est une lampe torche. Les projecteurs sont très utiles lorsque le personnage du jeu que vous développez est en train d'explorer un donjon souterrain, ou qu'il s'échappe d'une prison.

Nous connaissons déjà tous les outils pour implémenter le projecteur. La pièce manquante est l'effet de cône de ce type de lumière. Regardons l'image suivante :

Image non disponible

La direction de la source lumineuse est définie par la flèche qui pointe directement vers le bas. Nous voulons que notre lumière n'ait d'effet que dans l'aire délimitée par les deux lignes rouges. Le produit scalaire vient encore une fois à notre secours. Nous définissons le cône de lumière comme l'angle entre chacune des lignes rouges et la direction de la lumière (c'est-à-dire la moitié de l'angle entre les lignes rouges). Nous pouvons prendre le cosinus « C » de cet angle et effectuer un produit scalaire entre la direction de la lumière « L » et le vecteur « V » entre l'origine de la lumière et le pixel. Si le résultat du produit scalaire est plus grand que « C » (rappel : le cosinus augmente lorsque l'angle diminue), alors l'angle entre « L » et « V » est plus petit que l'angle entre « L » et les deux lignes rouges qui définissent le cône de lumière (l'angle d'ouverture). Dans ce cas, nous voulons que le pixel reçoive de la lumière. Si l'angle est plus large, le pixel ne reçoit plus de lumière de cette source. Dans l'exemple ci-dessus, un produit scalaire entre « L » et « V » va produire un résultat plus petit que le produit scalaire entre « L » et n'importe laquelle des deux lignes rouges (il est assez évident que l'angle entre « L » et « V » est plus grand que l'angle entre « L » et les lignes rouges). Par conséquent, le pixel est hors du cône lumineux et n'est donc pas illuminé par le projecteur.

Si nous continuons avec cette approche « reçoit/ne reçoit pas de lumière », tout ce que nous allons obtenir c'est un projecteur extrêmement artificiel, avec une démarcation très nette entre les zones éclairées et les zones sombres. Elle projettera un cercle parfait dans le noir (s'il n'y a pas d'autre source lumineuse). Un projecteur plus réaliste aura une lumière diminuant progressivement au niveau des bords du cercle. Nous pouvons utiliser le produit scalaire, que nous avons calculé (afin de déterminer si le pixel est éclairé ou pas), comme facteur. Nous savons déjà que le produit scalaire vaut 1 (donc éclairage maximum) lorsque les vecteurs « L » et « V » sont égaux, mais nous rencontrons maintenant l'effet indésirable de la fonction cosinus. L'angle d'ouverture ne doit pas être trop large, sinon la lumière sera diffusée trop largement et perdra l'apparence d'un projecteur. Par exemple, définissons l'angle à 20 degrés. Le cosinus de 20 degrés est 0.939, mais l'intervalle [0.939, 1.0] est trop petit pour servir de facteur. Il n'y a pas assez d'écart et les résultats de l'interpolation ne seront pas perceptibles par l'œil. L'intervalle [0, 1] fournit de bien meilleurs résultats.

L'approche que nous allons utiliser est de faire correspondre l'intervalle plus petit, défini par l'angle d'ouverture, à l'intervalle plus large [0, 1]. Voici comment nous allons procéder :

Image non disponible

Le principe est très simple : on calcule le ratio entre l'intervalle le plus petit et l'intervalle le plus grand, puis on multiplie l'intervalle par ce ratio.

II. Explication du code▲

lighting_technique.h
Sélectionnez
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
struct SpotLight : public PointLight
{
    Vector3f Direction;
    float Cutoff;

    SpotLight()
    {
        Direction = Vector3f(0.0f, 0.0f, 0.0f);
        Cutoff = 0.0f;
    }
};

La structure définissant le projecteur est dérivée de PointLight et ajoute les deux attributs qui la différencient d'une lumière ponctuelle : un vecteur direction et une valeur d'arrêt. La valeur d'arrêt correspond à l'angle maximum entre la direction de la lumière et le vecteur entre la source et le pixel, pour les pixels sous l'éclairage du projecteur. Le projecteur n'a pas d'effet au-delà de la valeur d'arrêt. Nous avons aussi ajouté un tableau de positions pour le shader, à la classe LightingTechnique (non décrit ici). Ce tableau nous permet d'accéder au tableau de projecteurs dans le shader.

lighting.fs
Sélectionnez
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
struct SpotLight
{
    struct PointLight Base;
    vec3 Direction;
    float Cutoff;
};
...
uniform int gNumSpotLights;
...
uniform SpotLight gSpotLights[MAX_SPOT_LIGHTS];

Une structure similaire est ajoutée en GLSL, pour les lumières de type projecteur. Comme nous ne pouvons pas utiliser l'héritage comme en C++, nous utilisons une structure PointLight en tant que membre et ajoutons les nouveaux attributs. La différence importante ici est que dans le code C++, la valeur d'arrêt est l'angle lui-même, alors que dans le shader c'est le cosinus de cet angle. Le shader n'a besoin que du cosinus, donc il est plus efficace de le calculer une fois et pas pour chaque pixel. Nous définissons aussi un tableau de projecteurs et utilisons un compteur nommé « gNumSpotLights »pour permettre à l'application de définir le nombre de projecteurs réellement utilisés.

lighting.fs
Sélectionnez
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
vec4 CalcPointLight(struct PointLight l, vec3 Normal)
{
    vec3 LightDirection = WorldPos0 - l.Position;
    float Distance = length(LightDirection);
    LightDirection = normalize(LightDirection);

    vec4 Color = CalcLightInternal(l.Base, LightDirection, Normal);
    float Attenuation = l.Atten.Constant +
        l.Atten.Linear * Distance +
        l.Atten.Exp * Distance * Distance;

    return Color / Attenuation;
}

La fonction de lumière ponctuelle a subi une légère modification : elle prend maintenant une structure PointLight en paramètre, plutôt que l'accès direct au tableau global. Cela permet de partager facilement cette fonction avec les projecteurs. À part ça, pas de changement ici.

lighting.fs
Sélectionnez
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
vec4 CalcSpotLight(struct SpotLight l, vec3 Normal)
{
    vec3 LightToPixel = normalize(WorldPos0 - l.Base.Position);
    float SpotFactor = dot(LightToPixel, l.Direction);

    if (SpotFactor > l.Cutoff) {
        vec4 Color = CalcPointLight(l.Base, Normal);
        return Color * (1.0 - (1.0 - SpotFactor) * 1.0/(1.0 - l.Cutoff));
    }
    else {
        return vec4(0,0,0,0);
    }
}

Ici nous calculons l'effet du projecteur. Nous commençons en récupérant le vecteur entre la source lumineuse et le pixel. Comme c'est souvent le cas, nous le normalisons afin de le préparer pour le produit scalaire qui suit. Nous calculons donc le produit scalaire entre ce vecteur et la direction de la lumière (qui a déjà été normalisée par l'application), et récupérons le cosinus de l'angle entre eux deux. Ensuite, nous le comparons à la valeur d'arrêt. Celle-ci représente le cosinus de l'angle entre la direction de la lumière et le vecteur qui définit le cercle d'influence du projecteur. Si le cosinus est plus petit, cela signifie que l'angle entre la direction de la lumière et le vecteur de la source au pixel place le vecteur hors du cercle d'influence. Dans ce cas, la contribution de ce projecteur vaut zéro. Cela limitera le projecteur à un cercle plus ou moins large, dépendant de la valeur d'arrêt. Sinon, nous calculons la couleur de base comme si la lumière était une lumière ponctuelle. Puis, nous récupérons le produit scalaire que nous avons calculé précédemment (« SpotFactor ») et l'introduisons dans la formule calculée plus haut. Cela fournit le facteur qui va interpoler linéairement « SpotFactor » entre 0 et 1. Nous le multiplions par la couleur de la lumière ponctuelle et obtenons la couleur finale du projecteur.

lighting.fs
Sélectionnez
122.
123.
124.
125.
126.
...
for (int i = 0 ; i < gNumSpotLights ; i++) {
    TotalLight += CalcSpotLight(gSpotLights[i], Normal);
}
...

De manière similaire aux lumières ponctuelles, nous avons, dans le programme principal, une boucle qui accumule la contribution de tous les projecteurs à la couleur finale du pixel.

lighting_technique.cpp
Sélectionnez
367.
368.
369.
370.
371.
372.
373.
374.
375.
376.
377.
378.
379.
380.
381.
382.
383.
384.
void LightingTechnique::SetSpotLights(unsigned int NumLights, const SpotLight* pLights)
{
    glUniform1i(m_numSpotLightsLocation, NumLights);

    for (unsigned int i = 0 ; i < NumLights ; i++) {
        glUniform3f(m_spotLightsLocation[i].Color, pLights[i].Color.x, pLights[i].Color.y, pLights[i].Color.z);
        glUniform1f(m_spotLightsLocation[i].AmbientIntensity, pLights[i].AmbientIntensity);
        glUniform1f(m_spotLightsLocation[i].DiffuseIntensity, pLights[i].DiffuseIntensity);
        glUniform3f(m_spotLightsLocation[i].Position, pLights[i].Position.x, pLights[i].Position.y, pLights[i].Position.z);
        Vector3f Direction = pLights[i].Direction;
        Direction.Normalize();
        glUniform3f(m_spotLightsLocation[i].Direction, Direction.x, Direction.y, Direction.z);
        glUniform1f(m_spotLightsLocation[i].Cutoff, cosf(ToRadian(pLights[i].Cutoff)));
        glUniform1f(m_spotLightsLocation[i].Atten.Constant, pLights[i].Attenuation.Constant);
        glUniform1f(m_spotLightsLocation[i].Atten.Linear, pLights[i].Attenuation.Linear);
        glUniform1f(m_spotLightsLocation[i].Atten.Exp, pLights[i].Attenuation.Exp);
    }
}

Cette fonction met à jour le tableau de SpotLight dans le shader. C'est la même fonction que pour les lumières ponctuelles, avec deux choses en plus. Le vecteur direction de la lumière est aussi donné au shader, après normalisation. La valeur d'arrêt est aussi fournie en tant qu'angle par l'appelant, mais son cosinus est donné au shader (ce qui permet au shader de comparer le produit scalaire à cette valeur, directement). Notons que la fonction cosf() prend comme paramètre un angle en radians donc nous utilisons la macro ToRadian pour le convertir.

III. Sources▲

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources

IV. Remerciements▲

Merci à Etay Meiri de nous permettre de traduire son tutoriel.

Merci à LittleWhite pour ses corrections et à Claude Leloup pour sa relecture.

Navigation▲

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+ inPartager

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2015 Etay Meiri. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite - Contacter par email
  • Nous contacter
  • Participez
  • Hébergement
  • Informations légales

Copyright © 2000-2016 - www.developpez.com


Tutoriel 22 - Chargement de modèles en utilisant la bibliothèque Open Asset Import Library

Table des matières Plier Déplier

  • I. Contexte
  • II. Explication du code
  • III. Sources
  • IV. Remerciements

Dans ce tutoriel, nous allons découvrir une bibliothèque de chargement de modèles à partir de fichiers.

Nous sommes arrivés jusqu'ici en utilisant des modèles générés manuellement. Comme vous vous en doutez, la méthode de spécifier manuellement la position et autres attributs pour chaque sommet d'un objet n'est pas très adaptée aux objets complexes. Ça va bien pour une boîte, une pyramide, et une simple surface en tuiles, mais qu'en est-il pour un visage humain ? Dans le monde des jeux vidéo et des applications commerciales, le procédé de création d'un maillage est pris en charge par des artistes, qui utilisent des applications telles que Blender, Maya et 3ds Max. Ces applications fournissent des outils avancés qui aident les artistes à créer des modèles extrêmement sophistiqués. Lorsque le modèle est achevé, il est enregistré dans un fichier, dans un des multiples formats disponibles. Le fichier contient la complète définition de la géométrie du modèle. Il peut maintenant être chargé dans un moteur de jeu (si tant est que le moteur supporte ce format particulier), et son contenu peut être utilisé pour remplir les tampons de sommets et d'indices, pour le rendu. Connaître la manière d'analyser le format de définition de la géométrie, et charger des modèles professionnels est crucial pour porter votre programmation 3D à un autre niveau.

Développer un analyseur par vous-même peut prendre beaucoup de votre temps. Si vous voulez être capable de charger des modèles depuis différentes sources, vous devez étudier chaque format et développer un analyseur spécifique à chaque fois. Certains formats sont simples, mais d'autres sont très complexes et vous pourriez finir en passant beaucoup trop de temps sur quelque chose qui s'éloigne du cœur de la programmation 3D. Par conséquent, l'approche décrite dans ce tutoriel est d'utiliser une bibliothèque externe pour prendre en charge l'analyse et le chargement des modèles à partir de fichiers.

La bibliothèque Open Asset Import, ou Assimp, est une bibliothèque open source, qui peut gérer de nombreux formats 3D, incluant les plus populaires. Elle est portable et disponible à la fois sous Windows et Linux. Elle est très simple à utiliser et s'intègre dans des applications écrites en C/C++.

Il n'y a pas beaucoup de théorie dans ce tutoriel. Voyons directement comment intégrer Assimp dans nos applications 3D. (Avant de débuter, vérifiez que vous avez installé Assimp à partir du lien ci-dessus.)

II. Explication du code▲

Télécharger Sélectionnez
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
class Mesh
{
public:
    Mesh();

    ~Mesh();

    bool LoadMesh(const std::string& Filename);

    void Render();

private:
    bool InitFromScene(const aiScene* pScene, const std::string& Filename);
    void InitMesh(unsigned int Index, const aiMesh* paiMesh);
    bool InitMaterials(const aiScene* pScene, const std::string& Filename);
    void Clear();

#define INVALID_MATERIAL 0xFFFFFFFF

    struct MeshEntry {
        MeshEntry();

        ~MeshEntry();

        bool Init(const std::vector& Vertices,
        const std::vector& Indices);

        GLuint VB;
        GLuint IB;
        unsigned int NumIndices;
        unsigned int MaterialIndex;
    };

    std::vector m_Entries;
    std::vector m_Textures;
};

La classe Mesh représente l'interface entre Assimp et notre application OpenGL. Un objet de cette classe prend un nom de fichier comme paramètre à la méthode LoadMesh(), utilise Assimp pour charger le modèle, puis crée les tampons de sommets et d'indices et les objets Texture, qui contiennent les données du modèle, dans un format que notre programme comprend. Afin de dessiner le maillage, nous utiliserons la méthode Render(). La structure interne de la classe Mesh correspond à la manière dont Assimp charge les modèles. Assimp utilise un objet aiScene pour représenter le maillage chargé. Cet objet aiScene contient les structures de maillage qui encapsulent des parties du modèle. Il doit y avoir au moins une structure de maillage dans l'objet aiScene. Les modèles complexes peuvent contenir de multiples maillages. L'entrée m_Entries de la classe Mesh est un vecteur de la structure MeshEntry, où chaque structure correspond à un maillage de l'objet aiScene. Cette structure contient le tampon de sommets, le tampon d'indices, et l'indice du matériau. Pour l'instant, un matériau est simplement une texture, et comme les maillages peuvent partager des matériaux, nous avons un vecteur séparé pour ceux-ci (m_Textures). MeshEntry::MaterialIndex pointe sur une des textures de m_Textures.

Télécharger Sélectionnez
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
bool Mesh::LoadMesh(const std::string& Filename)
{
    // Release the previously loaded mesh (if it exists)
    Clear();

    bool Ret = false;
    Assimp::Importer Importer;

    const aiScene* pScene = Importer.ReadFile(Filename.c_str(), aiProcess_Triangulate | aiProcess_GenSmoothNormals | aiProcess_FlipUVs);

    if (pScene) {
        Ret = InitFromScene(pScene, Filename);
    }
    else {
        printf("Error parsing '%s': '%s'\n", Filename.c_str(), Importer.GetErrorString());
    }

    return Ret;
}

Cette méthode est le point d'entrée du chargement du maillage. Nous créons une instance de la classe Assimp::Importer sur la pile et appelons sa méthode ReadFile(). Cette méthode prend deux paramètres : le chemin complet d'accès au fichier du modèle, et un masque d'actions en post-traitement. Assimp est capable d'effectuer de nombreuses actions utiles sur les modèles chargés. Par exemple, il peut générer les normales pour les modèles qui n'en ont pas, optimiser la structure du modèle pour améliorer les performances, etc. La liste complète des options est disponible ici. Dans ce tutoriel, nous utilisons trois options :

  • aiProcess_Triangulate, qui convertit les maillages faits de polygones non triangles en maillages constitués uniquement de polygones triangles. Par exemple, un maillage fait de rectangles peut être converti en maillage de triangles en créant deux triangles par rectangle ;
  • la seconde option, aiProcess_GenSmoothNormals, génère des normales aux sommets, dans le cas où le modèle original n'en contiendrait pas encore. Notez que les options de post-traitement sont des masques de bits, vous permettant d'appliquer diverses options en les combinant avec un OU logique ;
  • la dernière option, aiProcess_FlipUVs, inverse les coordonnées de texture le long de l'axe Y. Cette option est requise pour dessiner correctement le modèle de Quake utilisé pour la démonstration. Vous devrez probablement vérifier les options que vous allez utiliser, en fonction des modèles d'entrée. Si le maillage a été chargé correctement, vous obtenez un pointeur sur un objet aiScene. Cet objet contient toutes les données du modèle, divisées en aiMesh. Ensuite nous appelons la méthode InitFromScene(), pour initialiser l'objet Mesh.
Télécharger Sélectionnez
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
bool Mesh::InitFromScene(const aiScene* pScene, const std::string& Filename)
{
    m_Entries.resize(pScene->mNumMeshes);
    m_Textures.resize(pScene->mNumMaterials);

    // Initialise les maillages de la scène, un par un
    for (unsigned int i = 0 ; i < m_Entries.size() ; i++) {
        const aiMesh* paiMesh = pScene->mMeshes[i];
        InitMesh(i, paiMesh);
    }

    return InitMaterials(pScene, Filename);
}

Nous commençons l'initialisation de l'objet Mesh, en définissant l'espace occupé par les vecteurs de maillages et de textures, pour tous les maillages et matériaux dont nous allons avoir besoin. Les nombres sont donnés par les membres mNumMeshes et mNumMaterials de l'objet aiScene, respectivement. Ensuite, nous parcourons le tableau mMeshes de l'objet aiScene, et initialisons les entrées de maillage une à une. Enfin, nous initialisons les matériaux.

Télécharger Sélectionnez
111.
112.
113.
114.
115.
116.
117.
void Mesh::InitMesh(unsigned int Index, const aiMesh* paiMesh)
{
    m_Entries[Index].MaterialIndex = paiMesh->mMaterialIndex;

    std::vector Vertices;
    std::vector Indices;
    ...

Nous commençons l'initialisation du maillage, en stockant l'indice de son matériau. Il sera utilisé lors du rendu, pour utiliser la bonne texture. Ensuite, nous créons deux vecteurs STL, pour stocker le contenu des tampons de sommets et d'indices. Le vecteur STL possède la caractéristique intéressante de stocker son contenu dans un tampon continu en mémoire. Cela facilite le chargement des données dans un tampon OpenGL (en utilisant la fonction glBufferData()).

Télécharger Sélectionnez
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
    const aiVector3D Zero3D(0.0f, 0.0f, 0.0f);

    for (unsigned int i = 0 ; i < paiMesh->mNumVertices ; i++) {
        const aiVector3D* pPos = &(paiMesh->mVertices[i]);
        const aiVector3D* pNormal = &(paiMesh->mNormals[i]) : &Zero3D;
        const aiVector3D* pTexCoord = paiMesh->HasTextureCoords(0) ? &(paiMesh->mTextureCoords[0][i]) : &Zero3D;

        Vertex v(Vector3f(pPos->x, pPos->y, pPos->z),
                Vector2f(pTexCoord->x, pTexCoord->y),
                Vector3f(pNormal->x, pNormal->y, pNormal->z));

        Vertices.push_back(v);
    }
    ...

Ici, nous préparons le contenu du tampon de sommets, en remplissant le vecteur de Vertex. Nous utilisons les attributs suivants de la classe aiMesh :

  1. mNumVertices : le nombre de sommets ;
  2. mVertices : un tableau de mNumVertices aiVector3D contenant les positions ;
  3. mNormals : un tableau de mNumVertices aiVector3D contenant les normales ;
  4. mTextureCoords : un tableau de mNumVertices aiVector3D contenant les coordonnées de texture. C'est en fait un tableau à deux dimensions, car chaque sommet peut avoir plusieurs coordonnées de texture.

Donc, nous avons en fait trois tableaux séparés, contenant tout ce dont nous avons besoin pour les sommets, et nous devons prendre chaque attribut dans le tableau correspondant, afin de remplir une structure Vertex. Cette structure est ensuite mise en fin du vecteur de sommets (en gardant donc le même indice que dans les trois tableaux de aiMesh). Notez que certains modèles n'ont pas de coordonnées de texture, donc, avant d'accéder au tableau mTextureCoords (et par la même occasion, risquer de causer une erreur de segmentation), nous vérifions si les coordonnées de texture existent, en appelant la méthode HasTextureCoords(). En plus de cela, un maillage peut contenir plusieurs coordonnées de texture par sommet. Dans ce tutoriel, nous prenons le chemin facile, en n'utilisant que la première coordonnée de texture. Ainsi, on n'accède qu'à la première ligne du tableau mTextureCoords. Par conséquent, la méthode HasTextureCoords() est toujours appelée pour la première ligne. Si une coordonnée de texture n'existe pas, la structure Vertex sera initialisée avec le vecteur zéro.

Télécharger Sélectionnez
132.
133.
134.
135.
136.
137.
138.
139.
    for (unsigned int i = 0 ; i < paiMesh->mNumFaces ; i++) {
        const aiFace& Face = paiMesh->mFaces[i];
        assert(Face.mNumIndices == 3);
        Indices.push_back(Face.mIndices[0]);
        Indices.push_back(Face.mIndices[1]);
        Indices.push_back(Face.mIndices[2]);
    }
    ...

Ensuite nous créons le tampon d'indices. Le membre mNumFaces de la classe aiMesh nous indique le nombre de polygones existant, et le tableau mFaces contient leurs données (qui sont les indices des sommets). Nous vérifions que le nombre d'indices dans le polygone est bien trois (en chargeant le modèle, nous avons demandé à ce qu'il soit converti en triangles, mais c'est toujours bon de vérifier). Ensuite, nous extrayons les indices du tableau mIndices et les mettons en fin du vecteur d'indices.

Télécharger Sélectionnez
140.
141.
    m_Entries[Index].Init(Vertices, Indices);
}

Enfin, la structure MeshEntry est initialisée, en utilisant les vecteurs de sommets et d'indices. Il n'y a rien de nouveau dans la méthode MeshEntry::Init(), qui n'est donc pas listée ici. Elle utilise glGenBuffers(), glBindBuffer(), et glBufferData(), pour créer et remplir les tampons d'indices et de sommets. Regardez le fichier source pour plus de détails.

Télécharger Sélectionnez
143.
144.
145.
146.
147.
bool Mesh::InitMaterials(const aiScene* pScene, const std::string& Filename)
{
    for (unsigned int i = 0 ; i < pScene->mNumMaterials ; i++) {
        const aiMaterial* pMaterial = pScene->mMaterials[i];
       ...

Cette méthode charge toutes les textures utilisées par le modèle. L'attribut mNumMaterials de l'objet aiScene contient le nombre de matériaux, et mMaterials est un tableau de pointeurs sur des structures aiMaterialaiMaterialaiMaterial (de cette taille). La structure aiMaterial est une bestiole complexe, mais cache sa complexité derrière un nombre réduit de méthodes. En général, un matériau est organisé comme une pile de textures, et entre deux textures consécutives les fonctions de mélange et de puissance doivent être appliquées. Par exemple, la fonction de mélange peut nous dire d'ajouter la couleur de deux textures, et la fonction de puissance peut nous dire de diviser le résultat par deux. Les fonctions de mélange et de force font partie de la structure aiMaterial, et peuvent être récupérées. Pour nous faciliter la vie, et rester en accord avec la manière de fonctionner de notre shader d'éclairage, nous ignorons les fonctions de mélange et de puissance, et utilisons la texture telle quelle.

Télécharger Sélectionnez
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
        m_Textures[i] = NULL;
        if (pMaterial->GetTextureCount(aiTextureType_DIFFUSE) > 0) {
            aiString Path;

            if (pMaterial->GetTexture(aiTextureType_DIFFUSE, 0, &Path, NULL, NULL, NULL, NULL, NULL) == AI_SUCCESS) {
                std::string FullPath = Dir + "/" + Path.data;
                m_Textures[i] = new Texture(GL_TEXTURE_2D, FullPath.c_str());

                if (!m_Textures[i]->Load()) {
                    printf("Error loading texture '%s'\n", FullPath.c_str());
                    delete m_Textures[i];
                    m_Textures[i] = NULL;
                    Ret = false;
                }
            }
        }
        ...

Un matériau peut contenir plusieurs textures, et toutes ne contiennent pas des couleurs. Par exemple, une texture peut définir un relief, contenir des normales ou encore des déplacements, etc. Comme notre shader d'éclairage n'utilise pour l'instant qu'une seule texture pour tous les types de lumières, nous ne sommes intéressés que par la texture diffuse. Par conséquent, nous vérifions combien de textures diffuses existent, en utilisant la méthode aiMaterial::GetTextureCount(). Cette méthode prend le type de texture en paramètre, et retourne le nombre de textures de ce type spécifique. Si au moins une texture diffuse est disponible, nous la récupérons en utilisant la méthode aiMaterial::GetTexture(). Le premier paramètre de cette méthode est le type. Ensuite vient l'indice et nous utilisons toujours 0. Après cela, nous devons spécifier l'adresse d'une chaîne de caractères, où le nom du fichier de cette texture va être récupéré. Enfin, il y a cinq paramètres d'adresses, qui permettent de récupérer diverses configurations de la texture, telles que le facteur de mélange, le mode d'application, l'opération de texture, etc. Ceux-ci sont optionnels, et nous les ignorons pour l'instant, en passant NULL à chacun. Nous ne sommes intéressés que par le nom du fichier de la texture, et nous le concaténons au dossier où le modèle se trouve. Le dossier avait été récupéré au début de la fonction (non listée ici), et nous supposons que la texture et le modèle se trouvent dans le même dossier. Si la structure du dossier est plus compliquée, vous devrez chercher la texture autre part. Nous créons notre objet texture et le chargeons.

Télécharger Sélectionnez
187.
188.
189.
190.
191.
192.
193.
194.
       if (!m_Textures[i]) {
          m_Textures[i] = new Texture(GL_TEXTURE_2D, "../Content/white.png");
          Ret = m_Textures[i]->Load();
       }
    }

    return Ret;
}

Le morceau de code ci-dessus est un petit contournement d'un problème que vous pourrez rencontrer, si vous commencez à charger des modèles trouvés sur Internet. Parfois, le modèle n'inclut pas de texture et dans ce cas, vous ne verrez rien, car la couleur qui est échantillonnée à partir d'une texture inexistante est le noir, par défaut. Une manière de gérer cela est de détecter le cas, et de le traiter comme un cas spécial dans le shader ou dans un shader dédié. Ce tutoriel applique une approche plus simple, en chargeant une texture contenant un simple texel blanc (vous trouverez cette texture avec les sources attachées). Cela fera que la couleur par défaut des pixels sera le blanc. Ce ne sera probablement pas très joli par contre, au moins, vous verrez quelque chose. Cette texture prend vraiment très peu de place, et nous permet d'utiliser le même shader dans tous les cas.

Télécharger Sélectionnez
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
void Mesh::Render()
{
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);
    glEnableVertexAttribArray(2);

    for (unsigned int i = 0 ; i < m_Entries.size() ; i++) {
        glBindBuffer(GL_ARRAY_BUFFER, m_Entries[i].VB);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);
        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)12);
        glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)20);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_Entries[i].IB);

        const unsigned int MaterialIndex = m_Entries[i].MaterialIndex;

        if (MaterialIndex < m_Textures.size() && m_Textures[MaterialIndex]) {
            m_Textures[MaterialIndex]->Bind(GL_TEXTURE0);
        }

        glDrawElements(GL_TRIANGLES, m_Entries[i].NumIndices, GL_UNSIGNED_INT, 0);
    }

    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);
    glDisableVertexAttribArray(2);
}

Cette fonction encapsule le dessin d'un maillage, et le détache de l'application principale (dans les précédents tutoriels, il faisait partie du code de l'application elle-même). Le tableau m_Entries est parcouru, et les tampons de sommets et d'indices de chaque nœud sont liés au contexte OpenGL. L'indice du matériau utilisé par le nœud permet de récupérer l'objet texture, à partir du tableau m_Textures, et la texture est liée elle aussi. Enfin la commande de dessin est exécutée. Maintenant vous pouvez avoir plusieurs objets Mesh chargés depuis des fichiers, et les dessiner un par un, en appelant la méthode Mesh::Render.

Télécharger Sélectionnez
112.
glEnable(GL_DEPTH_TEST);

La dernière chose qu'il nous reste à étudier est quelque chose que nous avons laissé de côté lors des tutoriels précédents. Si vous continuez, et chargez des modèles en utilisant le code ci-dessus, vous observerez probablement des anomalies visuelles dans votre scène. La raison est que les triangles qui sont plus éloignés de la caméra sont dessinés au-dessus de ceux qui sont plus proches. Afin de prévenir cela, nous devons activer le fameux test de profondeur (aussi appelé Z-test). Lorsque le test de profondeur est activé, le rasterizer compare, avant son rendu, la profondeur de chaque pixel, par rapport à celle du pixel déjà dessiné à la même position sur l'écran. Le pixel dont la couleur est finalement choisie est celui qui a « gagné » le test de profondeur (c'est-à-dire le plus proche de la caméra). Le test de profondeur n'est pas activé par défaut, et la ligne ci-dessus s'occupe de cela (comme part de l'initialisation d'OpenGL dans la fonction GLUTBackendRun()). C'est une des trois portions de code nécessaires pour le test profondeur (voir plus bas).

Télécharger Sélectionnez
73.
glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA|GLUT_DEPTH);

La seconde portion est l'initialisation du tampon de profondeur. Afin de pouvoir comparer la profondeur entre deux pixels, la profondeur de « l'ancien » pixel doit être stockée quelque part (le tampon du « nouveau » pixel est disponible, car passé au vertex shader). Dans ce but, nous avons un tampon spécial connu sous le nom de tampon de profondeur (ou Z buffer). Il a les mêmes dimensions que l'écran, afin que chaque pixel du tampon de couleurs ait un emplacement correspondant dans le tampon de profondeur. Cet emplacement stocke toujours la profondeur du pixel le plus proche et il est utilisé dans le test de profondeur pour la comparaison.

Télécharger Sélectionnez
101.
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

La dernière chose que nous devons faire est de vider le tampon de profondeur au début de chaque image rendue. Si nous ne le faisons pas, le tampon contiendra les vieilles valeurs de l'image précédente, et la profondeur des pixels de la nouvelle image sera comparée à la profondeur des pixels de l'image précédente. Comme vous pouvez l'imaginer, cela va causer de sérieuses corruptions (essayez !). La fonction glClear() prend un masque de bits des tampons sur lesquels elle doit opérer. Jusqu'ici, nous n'avions vidé que le tampon de couleurs. Il est maintenant temps de vider aussi le tampon de profondeur.

III. Sources▲

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources

IV. Remerciements▲

Merci à Etay Meiri de nous permettre de traduire son tutoriel.

Merci à LittleWhite pour ses corrections et à milkoseck et ClaudeLELOUP pour leur relecture.

Navigation▲

Tutoriel précédent : projecteurs

Sommaire

Tutoriel suivant : shadow mapping (première partie)

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+ inPartager

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2015 Etay Meiri. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite - Contacter par email
  • Nous contacter
  • Participez
  • Hébergement
  • Informations légales

Copyright © 2000-2016 - www.developpez.com


Tutoriel 23 - Shadow-mapping

Partie 1

Table des matières Plier Déplier

  • I. Contexte
  • II. Explication du code
  • III. Sources
  • IV. Remerciements

Le concept d'ombre est inséparable du concept de lumière : nous avons besoin de lumière afin de pouvoir projeter une ombre. Il y a de nombreuses techniques pour générer des ombres, et dans ce tutoriel en deux parties, nous allons étudier l'une des plus simples et basiques : le Shadow Mapping.

Lorsqu'arrive le moment de la rastérisation et des ombres, la question qui vous vient à l'esprit est : est-ce que ce pixel est dans l'ombre, ou non ? Posons cette question différemment : est-ce que le chemin de la source lumineuse vers le pixel passe au travers d'un autre objet, ou non ? Si c'est le cas, le pixel est probablement dans l'ombre (en supposant que l'objet en question ne soit pas transparent…), et s'il ne le fait pas, le pixel n'est pas dans l'ombre. En un sens, cette question est proche de la question que nous nous posions dans le précédent tutoriel : comment s'assurer que, lorsque deux objets se chevauchent, nous voyions le plus proche ? Si nous plaçons, pour un instant, la caméra à l'origine de la lumière, les deux questions n'en sont plus qu'une. Nous voulons que les pixels qui échouent le test de profondeur (c'est-à-dire ceux qui sont trop éloignés, ou qui ont d'autres pixels devant eux) soient dans l'ombre. Seuls les pixels qui gagnent le test de profondeur doivent être dans la lumière. Ils sont ceux qui sont en contact direct avec la source lumineuse et rien ne les dissimule. Il s'agit là du cœur de l'idée derrière le shadow mapping.

Il semble donc que le test de profondeur peut nous aider à déterminer si un pixel est dans l'ombre, mais il y a un problème : la caméra et la lumière ne sont pas toujours situées à la même position. Le test de profondeur est habituellement utilisé pour résoudre les problèmes de visibilité, depuis le point de vue de la caméra donc, comment pouvons-nous l'utiliser pour la détection des ombres, lorsque la source lumineuse est placée plus loin ? La solution est de dessiner la scène deux fois. Tout d'abord, du point de vue de la source lumineuse. Les résultats de cette passe de rendu n'atteignent pas le tampon de couleurs. Par contre, les valeurs de profondeur les plus proches sont dessinées dans un tampon de profondeur, créé par l'application (au lieu de celui automatiquement généré par GLUT). Durant la seconde passe, la scène est dessinée, comme d'habitude, selon le point de vue de la caméra. Le tampon de profondeur que nous avons créé est lié au fragment shader, en lecture. Pour chaque pixel, nous récupérons la profondeur correspondante dans ce tampon de profondeur. Nous calculons aussi la profondeur de ce pixel du point de vue de la lumière. Parfois, les deux valeurs de profondeur seront identiques. C'est le cas où ce pixel était plus proche de la lumière, donc sa valeur de profondeur a fini dans le tampon de profondeur. Si ça arrive, nous considérons ce pixel comme éclairé, et calculons sa couleur comme d'habitude. Si les valeurs de profondeur sont différentes, cela signifie qu'un autre pixel couvrait celui-ci, du point de vue de la source lumineuse. Dans ce cas, nous ajoutons le facteur d'ombre au calcul de la lumière pour simuler l'effet d'ombre. Regardons l'image suivante :

Image non disponible

Notre scène est constituée de deux objets : la surface, et le cube. La source lumineuse est située dans le coin en haut à gauche, et est dirigée vers ce cube. Pendant la première passe, nous dessinons dans le tampon de profondeur, du point de vue de la source lumineuse. Attardons-nous sur les trois points A, B, et C. Lorsque B est dessiné, sa valeur de profondeur va dans le tampon de profondeur. La raison est qu'il n'y a rien, entre le point et la lumière. Par défaut, il est le point le plus proche de la lumière sur cette ligne. Par contre, lorsque A et C sont dessinés, ils « bataillent » exactement sur le même point du tampon de profondeur. Les deux points sont sur la même ligne depuis la source lumineuse, donc après que la projection en perspective a été appliquée, le rastériseur trouve que deux points doivent aller sur le même pixel à l'écran. C'est le test de profondeur, C le « gagne ».

Durant la seconde passe, nous dessinons la surface et le cube, du point de vue de la caméra. Dans notre shader d'éclairage par pixel, en plus de tout ce que nous faisons déjà, nous allons aussi calculer la distance entre la source lumineuse et le pixel, et la comparer à la valeur correspondante dans le tampon de profondeur. Lorsque nous calculons le point B, les deux valeurs devraient être globalement les mêmes (des différences sont attendues, dues aux interpolations, ainsi qu'aux problèmes de précision sur les nombres flottants). Ainsi, nous déterminons que B n'est pas dans l'ombre, et agissons en conséquence. Lorsque nous rastérisons A, nous trouvons que la valeur de profondeur stockée est nettement plus petite que la profondeur de A. Par conséquent, nous déterminons que A est dans l'ombre, et appliquons un certain facteur d'ombre pour le dessiner plus sombre que d'habitude.

Les sources de ce tutoriel incluent un simple maillage en rectangle, pouvant être utilisé pour afficher la shadow map. Le rectangle est constitué de deux triangles, et les coordonnées de texture sont définies pour que la texture couvre toute la surface. Lorsque le rectangle est dessiné, les coordonnées de texture sont interpolées par le rastériseur, vous permettant d'échantillonner une texture complète, et de l'afficher sur l'écran.

Télécharger Sélectionnez
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
class ShadowMapFBO
{
    public:
        ShadowMapFBO();

        ~ShadowMapFBO();

        bool Init(unsigned int WindowWidth, unsigned int WindowHeight);

        void BindForWriting();

        void BindForReading(GLenum TextureUnit);

    private:
        GLuint m_fbo;
        GLuint m_shadowMap;
};

Les résultats du pipeline 3D d'OpenGL finissent dans quelque chose qui est appelé « Framebuffer Object », tampon d'image en français (et aussi agrégé FBO). Ce concept englobe le tampon de couleurs (qui est affiché sur l'écran), le tampon de profondeur, ainsi que quelques autres tampons pour d'autres utilisations. Lorsque glutInitDisplayMode() est appelée, elle crée le tampon d'image par défaut, en utilisant les paramètres spécifiés. Ce tampon d'image est géré par le système de fenêtrage et ne peut pas être détruit par OpenGL. En plus du tampon d'image par défaut, une application peut créer des FBO elle-même. Ces objets peuvent être manipulés et utilisés dans diverses techniques, sous le contrôle de l'application. La classe ShadowMapFBO fournit une interface simple d'utilisation vers un FBO, qui va être utilisée pour la technique de shadow mapping. En interne, cette classe contient deux identifiants OpenGL. L'identifiant « m_fbo » représente le FBO. Le FBO encapsule l'état complet du tampon d'image. Une fois cet objet créé et configuré correctement, nous pouvons changer de tampon d'image simplement en attachant un objet différent.

Seul le tampon d'image par défaut peut être utilisé pour dessiner quelque chose sur l'écran. Les tampons d'image créés par l'application ne peuvent être utilisés que pour du rendu « hors écran ». Cela peut être une passe de rendu intermédiaire (par exemple : notre tampon de shadow mapping), qui sera utilisée plus tard pour la passe de rendu « réel » sur l'écran.

En lui-même, le tampon d'image n'est qu'un espace réservé. Pour le rendre utilisable, nous devons attacher des textures, à un ou plusieurs points d'attache. Les textures contiennent l'espace de stockage réel du tampon d'image. OpenGL définit les points d'attache suivants :

  1. COLOR_ATTACHMENTi : la texture qui sera attachée ici recevra la couleur provenant du fragment shader. Le suffixe « i » signifie qu'il peut y avoir plusieurs textures attachées aux attaches de couleur simultanément. Il y a un mécanisme dans le fragment shader permettant le rendu dans plusieurs tampons de couleur en même temps ;
  2. DEPTH_ATTACHMENT : la texture qui sera attachée ici recevra les résultats du test de profondeur ;
  3. STENCIL_ATTACHMENT : la texture attachée ici servira de tampon de découpe. Le tampon de découpe permet de limiter la zone de rastérisation, et peut être utilisé dans diverses techniques ;
  4. DEPTH_STENCIL_ATTACHMENT : celui-ci est simplement une combinaison d'un tampon de profondeur et d'un tampon de stencil, car les deux sont souvent utilisés conjointement.

Pour la technique de shadow mapping, nous n'avons besoin que d'un tampon de profondeur. Le membre « m_shadowMap » est l'identifiant de la texture qui sera attachée au point DEPTH_ATTACHMENT. La classe ShadowMapFBO fournit aussi quelques méthodes qui seront utilisées dans la fonction principale de dessin. Nous appellerons BindForWriting() avant le dessin dans la shadow map, et BindForReading() lors du début de la seconde passe de dessin.

Télécharger Sélectionnez
43.
glGenFramebuffers(1, &m_fbo);

Ici, nous créons le FBO. De même que pour les textures et les tampons, nous donnons l'adresse d'un tableau de GLuint et sa taille. Le tableau est rempli avec les identifiants.

Télécharger Sélectionnez
46.
47.
48.
49.
50.
51.
52.
glGenTextures(1, &m_shadowMap);
glBindTexture(GL_TEXTURE_2D, m_shadowMap);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, WindowWidth, WindowHeight, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

Ensuite, nous créons la texture qui va servir de shadow map. De manière générale, c'est une texture 2D standard, avec quelques options de configuration spécifiques pour la rendre utilisable dans ce contexte :

  1. Le format interne est GL_DEPTH_COMPONENT. C'est différent des précédentes utilisations de cette fonction où le format interne est habituellement un des types de couleurs (par exemple : RGB). GL_DEPTH_COMPONENT signifie un seul nombre flottant qui représente la profondeur normalisée ;
  2. Le dernier paramètre de glTexImage2D est nul. Cela signifie que nous ne fournissons pas de données pour initialiser le tampon. C'est logique, sachant que nous voulons que le tampon contienne les valeurs de profondeur de chaque frame, et chaque frame est différente. À chaque fois que nous entamons une nouvelle frame, nous utilisons glClear() pour vider le tampon. C'est toute l'initialisation dont nous avons besoin pour le contenu ;
  3. Nous disons à OpenGL que si les coordonnées de texture dépassent les bornes, nous les remettons dans l'intervalle [0, 1]. Cela arrive lorsque la fenêtre de projection depuis le point de vue de la caméra contient plus que la fenêtre de projection du point de vue de la source lumineuse. Pour éviter des artefacts étranges, tels que l'ombre qui se répète autre part (dû à l'enroulement), nous imposons qu'elles soient remises dans cet intervalle.
Télécharger Sélectionnez
54.
glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);

Nous avons généré le FBO, l'objet de texture, et nous avons configuré cet objet pour le shadow mapping. Maintenant, nous devons attacher l'objet de texture au FBO. La première chose à faire pour cela est d'activer le FBO. Cela le définit comme « courant », et toutes les opérations pour FBO à venir s'appliqueront à celui-ci. Cette fonction prend l'identifiant du FBO et la cible voulue. La cible peut être GL_FRAMEBUFFER, GL_DRAW_FRAMEBUFFER ou GL_READ_FRAMEBUFFER. GL_READ_FRAMEBUFFER est utilisé lorsque nous voulons lire depuis le FBO en utilisant glReadPixels() (pas dans ce tutoriel). GL_DRAW_FRAMEBUFFER est utilisé lorsque nous voulons dessiner dans le FBO. Lorsque nous utilisons GL_FRAMEBUFFER, et l'état d'écriture, et l'état de lecture sont mis à jour et c'est la méthode recommandée pour initialiser le FBO. Nous allons utiliser GL_DRAW_FRAMEBUFFER lorsque nous commencerons effectivement à dessiner.

Télécharger Sélectionnez
55.
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_shadowMap, 0);

Ici, nous attachons la texture de shadow map au point d'attache de profondeur du FBO. Le dernier paramètre de cette fonction indique quelle couche de mipmap utiliser. Le mipmapping est une fonctionnalité de placage de texture, où une texture est représentée à différentes résolutions, en commençant par la résolution la plus haute, au mipmap 0, et en allant vers des résolutions inférieures, au mipmaps 1 à N. La combinaison de textures avec des mipmaps et le filtre trilinéaire fournit des résultats plus jolis, lors de la combinaison de texels provenant de niveaux de mipmap voisins (lorsqu'aucun niveau ne convient parfaitement). Ici nous avons un unique niveau de mipmap, nous utilisons donc 0. Nous fournissons l'identifiant de la shadow map en quatrième paramètre. Si nous utilisons 0 ici, nous détachons la texture du point d'attache (profondeur, dans notre cas).

Télécharger Sélectionnez
58.
59.
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);

Comme nous n'allons pas dessiner dans le tampon de couleurs (seulement dans celui de profondeur), nous le spécifions explicitement avec les appels ci-dessus. Par défaut, le tampon de couleurs est attaché au point GL_COLOR_ATTACHMENT0, mais notre FBO ne contient même pas de tampon de couleurs. Par conséquent, il est mieux de dire explicitement notre intention à OpenGL. Les paramètres valides pour ces fonctions sont GL_NONE et GL_COLOR_ATTACHMENT0 à GL_COLOR_ATTACHMENTm, où « m » vaut GL_MAX_COLOR_ATTACHMENTS - 1. Ces paramètres sont valides uniquement pour des FBO. Si le tampon d'image par défaut est utilisé, les paramètres valides sont alors GL_NONE, GL_FRONT_LEFT, GL_FRONT_RIGHT, GL_BACK_LEFT et GL_BACK_RIGHT. Ces paramètres vous permettent de dessiner directement dans les tampons avant et arrière (où chacun à un tampon droite et gauche). Nous définissons aussi le tampon de lecture à GL_NONE (souvenez-vous, nous n'allons pas appeler les fonctions glReadPixel()). C'est principalement pour éviter les problèmes liés aux GPU ne supportant qu'OpenGL 3.x et pas 4.x.

Télécharger Sélectionnez
61.
62.
63.
64.
65.
66.
GLenum Status = glCheckFramebufferStatus(GL_FRAMEBUFFER);

if (Status != GL_FRAMEBUFFER_COMPLETE) {
    printf("FB error, status: 0x%x\n", Status);
    return false;
}

Lorsque la configuration du FBO est terminée, il est très important de vérifier que son état est ce que la spécification d'OpenGL définit comme « complet ». Cela signifie qu'aucune erreur n'a été détectée, et que le tampon d'image peut maintenant être utilisé. Le code ci-dessus vérifie cela.

Télécharger Sélectionnez
72.
73.
74.
75.
void ShadowMapFBO::BindForWriting()
{
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fbo);
}

Nous allons avoir besoin de passer du rendu dans la shadow map au rendu dans le tampon d'image par défaut. Dans la seconde passe, nous avons aussi besoin d'attacher notre shadow map en entrée. Cette fonction et la suivante vont nous faciliter ces tâches. La fonction ci-dessus lie le FBO en écriture, comme nous l'avons fait auparavant. Nous l'appellerons avant la première passe de rendu…

Télécharger Sélectionnez
78.
79.
80.
81.
82.
void ShadowMapFBO::BindForReading(GLenum TextureUnit)
{
    glActiveTexture(TextureUnit);
    glBindTexture(GL_TEXTURE_2D, m_shadowMap);
}

… Et cette fonction sera appelée avant la seconde passe, pour lier la shadow map en lecture.

Notez que nous lions spécifiquement l'objet de texture, plutôt que le FBO lui-même.

Cette fonction prend en paramètre l'unité de texture à laquelle la shadow map doit être liée. L'indice de l'unité de texture doit être synchronisé avec le shader (car le shader possède une variable uniforme sampler2D pour accéder à la texture). Il est très important de noter qu'alors que glActiveTexture() prend l'indice de la texture en tant qu'énumération (c'est-à-dire GL_TEXTURE0, GL_TEXTURE1, etc.), le shader n'a lui besoin que de l'indice lui-même (0, 1, etc.). Cela peut être la source de nombreux bogues (croyez-moi, je le sais…).

Télécharger Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
#version 330

layout (location = 0) in vec3 Position;
layout (location = 1) in vec2 TexCoord;
layout (location = 2) in vec3 Normal;

uniform mat4 gWVP;

out vec2 TexCoordOut;

void main()
{
    gl_Position = gWVP * vec4(Position, 1.0);
    TexCoordOut = TexCoord;
}

Nous allons utiliser le même programme de shader pour les deux passes. Le vertex shader sera utilisé par les deux passes, alors que le fragment shader ne sera utilisé que par la seconde passe. Comme nous désactivons l'écriture dans le tampon de couleurs durant la première passe, le fragment shader sera tout simplement inutilisé. Le vertex shader ci-dessus est très simple. Il génère les coordonnées dans l'espace de découpe, en multipliant la position en coordonnées locales par la matrice WVP (World View Position), et transmet les coordonnées de texture. Pendant la première passe, les coordonnées de texture sont redondantes (du fait de l'absence de fragment shader). Cependant, cela n'a pas d'impact réel, et il est plus simple de partager le vertex shader. Comme vous pouvez le voir, le fait que ce soit la passe de profondeur ou la passe de rendu réel ne fait aucune différence, du point de vue du shader. Ce qui diffère, c'est que l'application donne une matrice WVP correspondant au point de vue de la lumière, pendant la première passe, et une matrice WVP correspondant au point de vue de la caméra pendant la seconde passe. Dans la seconde passe, nous avons aussi besoin des coordonnées de texture dans le fragment shader, car nous échantillonnons depuis la shadow map (qui est alors une entrée du shader).

Télécharger Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
#version 330

in vec2 TexCoordOut;
uniform sampler2D gShadowMap;

out vec4 FragColor;

void main()
{
    float Depth = texture(gShadowMap, TexCoordOut).x;
    Depth = 1.0 - (1.0 - Depth) * 25.0;
    FragColor = vec4(Depth);
}

Ceci est le fragment shader utilisé pour afficher la shadow map durant la passe de rendu. Les coordonnées de texture 2D sont utilisées pour récupérer la valeur de profondeur depuis la shadow map. La texture de shadow map a été créée avec comme format interne le type GL_DEPTH_COMPONENT. Cela signifie que le texel basique est un simple nombre flottant, et non une couleur. C'est pourquoi « .x » est utilisé durant l'échantillonnage. La projection en perspective possède la caractéristique connue de réserver, pour les positions proches de la caméra, plus de valeurs de l'intervalle [0, 1] que pour les positions lointaines, lors de la normalisation de la composante Z du vecteur position. La raison est de permettre une plus grande précision en profondeur lorsque nous nous approchons de la caméra, car les erreurs sont plus facilement visibles. Lorsque nous affichons le contenu du tampon de profondeur, nous pouvons tomber dans le cas où l'image résultante n'est pas assez claire. Par conséquent, après avoir échantillonné la profondeur depuis la shadow map, nous l'accentuons en multipliant la distance entre le point courant et le point le plus loin (pour lequel Z vaut 1.0) et en soustrayant le résultat de 1.0. Cela augmente l'intervalle, et améliore l'image finale. Nous utilisons la nouvelle valeur de profondeur pour créer une couleur, en la propageant dans toutes les composantes de la couleur. Cela signifie que nous allons obtenir des variations de gris (le blanc étant le plan de découpe éloigné, et le noir étant le plan de découpe proche).

Maintenant, voyons comment combiner les morceaux de code ci-dessus, et créons l'application.

Télécharger Sélectionnez
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
virtual void RenderSceneCB()
{
    m_pGameCamera->OnRender();
    m_scale += 0.05f;

    ShadowMapPass();
    RenderPass();

    glutSwapBuffers();
}

La fonction principale de rendu est devenue plus simple, car la plupart des fonctionnalités ont été déplacées dans d'autres fonctions. Tout d'abord, nous nous occupons des configurations « globales », telles que la mise à jour de la position de la caméra, et le membre de classe utilisé pour orienter l'objet. Ensuite nous appelons la fonction pour dessiner dans la shadow map, puis la fonction pour afficher le résultat. Enfin, glutSwapBuffers() est appelée pour l'afficher sur l'écran.

Télécharger Sélectionnez
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
virtual void ShadowMapPass()
{
    m_shadowMapFBO.BindForWriting();

    glClear(GL_DEPTH_BUFFER_BIT);

    Pipeline p;
    p.Scale(0.1f, 0.1f, 0.1f);
    p.Rotate(0.0f, m_scale, 0.0f);
    p.WorldPos(0.0f, 0.0f, 5.0f);
    p.SetCamera(m_spotLight.Position, m_spotLight.Direction, Vector3f(0.0f, 1.0f, 0.0f));
    p.SetPerspectiveProj(20.0f, WINDOW_WIDTH, WINDOW_HEIGHT, 1.0f, 50.0f);
    m_pShadowMapTech->SetWVP(p.GetWVPTrans());

    m_pMesh->Render();

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

Nous débutons la passe de shadow map en liant le FBO de la shadow map. À partir de maintenant, toutes les valeurs de profondeur iront dans notre texture de shadow map, et les écritures dans le tampon de couleurs seront défaussées. Nous nettoyons le tampon de profondeur (et seulement lui), avant de commencer à faire quoi que ce soit. Ensuite nous préparons la classe de pipeline, afin de dessiner le maillage (un char d'assaut provenant de Quake 2 est fourni avec les sources du tutoriel). Le seul point important ici est que la caméra est mise à jour, en se basant sur la position et l'orientation du projecteur de lumière. Nous dessinons le maillage, puis revenons au tampon d'image par défaut en liant le FBO zéro.

Télécharger Sélectionnez
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
virtual void RenderPass()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    m_pShadowMapTech->SetTextureUnit(0);
    m_shadowMapFBO.BindForReading(GL_TEXTURE0);

    Pipeline p;
    p.Scale(5.0f, 5.0f, 5.0f);
    p.WorldPos(0.0f, 0.0f, 10.0f);
    p.SetCamera(m_pGameCamera->GetPos(), m_pGameCamera->GetTarget(), m_pGameCamera->GetUp());
    p.SetPerspectiveProj(30.0f, WINDOW_WIDTH, WINDOW_HEIGHT, 1.0f, 50.0f);
    m_pShadowMapTech->SetWVP(p.GetWVPTrans());
    m_pQuad->Render();
}

La passe de rendu débute en nettoyant les deux tampons, couleurs et profondeur. Ces tampons appartiennent au tampon d'image par défaut. Nous disons au shader d'utiliser l'unité de texture 0, et lions la texture contenant la shadow map, en lecture à l'unité de texture 0. À partir de là, c'est comme d'habitude. Nous agrandissons le rectangle, nous le plaçons directement devant la caméra, et nous le dessinons. Pendant la rastérisation, la shadow map est échantillonnée, et affichée.

Dans ce tutoriel, nous ne chargeons plus automatiquement une texture blanche lorsque le fichier du maillage n'en spécifie pas. C'est pour être capable de lier la shadow map à la place. Si un maillage ne contient pas de texture, nous pouvons tout simplement n'en attacher aucune, cela permet au code appelant d'attacher sa propre texture.

III. Sources▲

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources

IV. Remerciements▲

Merci à Etay Meiri de nous permettre de traduire son tutoriel.

Merci à LittleWhite pour ses corrections et à Claude Leloup pour sa relecture.

Navigation▲

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+ inPartager

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2015 Etay Meiri. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite - Contacter par email
  • Nous contacter
  • Participez
  • Hébergement
  • Informations légales

Copyright © 2000-2016 - www.developpez.com


Programmation de Jeux 2D: Un pong en SDL/OpenGL

Si vous avez suivi la partie sur le morpion, vous avez eu l'occasion d'avoir un avant goût de ce qui peut se retrouver dans un programme de jeu. Le morpion était un bon premier programme puisqu'il ne posséde pas d'animations, il ne faut pas se soucier de performances (du moins pas énormément) et on avait donc la liberté de s'intéresser plus facilement à l'esthétique du jeu, sans se perdre dans les détails. Dans un jeu de pong, il y a beaucoup plus à gérer. On doit arriver à garder le nombre d'images par secondes assez élevé (certes, ce n'est pas trop difficile) mais nous allons nous compliquer la tâche. En effet, cette série va proposer de montrer comment faire un pong qui aura plusieurs balles et sera en multi-joueur ! Bonne lecture!

Article lu 9424 fois.

L'auteur

Jean Christophe Beyler Site personnel

L'article

Publié le 18 septembre 2006 - Mis à jour le 7 octobre 2006

Liens sociaux

Viadeo Twitter Facebook Share on Google+ inPartager

Bienvenue à ce groupe de tutoriels sur le pong. Cette série sera plus compléte que celle sur le morpion puisque nous allons nous attaquer sur un jeu plus difficile. En espérant qu'ils répondront à vos attentes concernant la programmation de ce jeu, je vous laisse et vous souhaite une bonne lecture.

Tutoriels
  • Introduction;
  • Les bases du moteur;
  • Les collisions et un menu;
  • Améliorer les collisions; Image non disponible
  • Le score, la souris et les joueurs;
  • Le réseau;
  • Conclusion.

Pour toute remarque, suggestion, question ou correction, vous pouvez me contacter par message privé.

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2006 Jean Christophe Beyler. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -
  • Nous contacter
  • Participez
  • Hébergement
  • Informations légales

Copyright © 2000-2016 - www.developpez.com


Programmation de Jeux 2D: Un pong en SDL/OpenGL, Première partie

1. Introduction▲

Le jeu de pong est sûrement un des jeux les plus refait. Cette série de tutoriels va présenter une implémentation possible mais en rendant la tâche plus difficile. En effet, nous voulons avoir plusieurs balles qui peuvent être en collision et un jeu en multi-joueur. Voici l'introduction de ce tutoriel, il montre comment ouvrir une fenêtre SDL/OpenGL.

Bienvenue à cette nouvelle série sur la programmation de jeux en 2D. Nous allons nous lancer dans un projet nettement plus intéressant que le morpion. En effet, nous allons présenter le jeu Pong.

La version de base consiste à avoir deux raquettes et une balle. Il faut faire passer la balle derrière la raquette de l'adversaire et à ce moment là c'est gagné!

Encore un jeu suffisamment simple pour ne pas nous faire perdre nos cheveux mais nous allons tout de même aller un peu plus loin. Premièrement, nous allons ajouter le fait que le jeu va ajouter une balle de temps en temps, ce qui va bien sûr compliquer le jeu. Ensuite, nous allons ajouter une couche réseau pour pouvoir jouer à ce jeu en multijoueur.

1.1. SDL▲

Je vais bien sûr reprendre beaucoup de choses de la série sur le morpion, je vous conseille donc de le lire si vous n'êtes pas familier avec la bibliothèque SDL ou avec la programmation de jeu en général.

La bibliothèque SDL permet d'ouvrir la fenêtre et de gérer les entrées claviers et souris. Nous allons revoir rapidement dans la section 2 comment ouvrir la fenêtre. Mais, contrairement au morpion, nous allons utiliser la bibliothèque OpenGL pour l'affichage.

1.2. OpenGL▲

On pourra se demander pourquoi utiliser OpenGL pour faire de la 2D? Pourquoi ne pas utiliser les fonctions fournies par SDL?

Les réponses sont assez simples à comprendre : nous voulons afficher des balles de tailles différentes et de couleurs différentes.

La première solution serait d'utiliser une image comme celle-ci :

Image non disponible Une image d'une balle

Ensuite, lors du rendu on utilise un filtre de couleur pour dessiner la balle dans la couleur voulue, et on modifie la taille pour obtenir les dimensions souhaitées.

La bibliothèque SDL ne permet pas facilement (et rapidement) de faire ces modifications, par contre c'est très facile en OpenGL.

La deuxième solution serait d'avoir une image contenant toutes les tailles et couleurs des balles souhaitées. Inutile de vous dire que ce serait un vrai travail de titan et on limiterait le programme inutilement.

Nous avons donc choisi d'utiliser SDL pour la gestion de la fenêtre et OpenGL pour le rendu.

2. Programme de base▲

Cette section va montrer comment ouvrir une fenêtre SDL/OpenGL correctement initialisée. Nous allons commencer par une fonction qui gère l'affichage OpenGL.

Fonction d'affichage
Sélectionnez

void affiche()
{
   //Mettre a zero le tampon de couleurs
   glClear(GL_COLOR_BUFFER_BIT);
   glLoadIdentity();

   SDL_GL_SwapBuffers();
}

Comme tout programme OpenGL, pour l'affichage, on utilise la fonction glClear pour mettre le tampon de couleur à zéro. C'est comme cela qu'on efface toute l'image qui se trouve dans le tampon. Cela nous permet donc de dessiner de nouveau sur le tampon. Ensuite, on remet le repère à zéro utilisant glLoadIdentity et, lorsque le code du dessin est terminé, on met à jour les tampons avec SDL_GL_SwapBuffers.

Ce tutoriel n'est en rien un tutoriel sur OpenGL. On suppose que vous connaissez un minimum sur OpenGL pour que cette fonction affiche ne soit pas une surprise. La suite de ce tutoriel est une présentation rapide de comment ouvrir une fenêtre OpenGL avec la bibliothèque SDL, pour avoir plus de détail sur les fonctions d'initialisation SDL, regardez le premier tutoriel SDL sur le morpion ou les tutoriels de loka.

La fonction main commence par des déclarations de variables locales.

Début du main
Sélectionnez

int main(int argc, char **argv)
{
    SDL_Event event;
    SDL_Surface *screen;
    int done = 0;
    int fps, last,now;

Voici la signification de ces variables :

  • event : pour gérer les événements;
  • screen : pour gérer la surface de la fenêtre;
  • done : pour savoir si le programme devait se terminer;
  • fps : pour calculer le nombre d'affichage par seconde;
  • last, now : pour gérer le temps (pour le calcul de fps).

Une fois les variables déclarées, nous allons commencer par initialiser SDL.

Initialisation
Sélectionnez

    //Initialisation
    if(SDL_Init(SDL_INIT_VIDEO)!=0) {
       cerr << "Probleme pour initialiser SDL: " << SDL_GetError() << endl;
       return EXIT_FAILURE;
    }

    //Mettre un titre à la fenetre
    SDL_WM_SetCaption("Pong Version 1.0", NULL);

    //Double tampon
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER,1);

    //Ouvrir une fenetre
    screen = SDL_SetVideoMode(WIDTH, HEIGHT, 32, SDL_OPENGL);
    if(screen==NULL)
          done = 1;

Comme vous le voyez, on initialise SDL, on met un titre à la fenêtre. Ensuite, la fonction SDL_GL_SetAttribute permet de dire qu'on utilise un double tampon pour l'affichage (sous SDL pur, nous utilisons le drapeau SDL_DOUBLEBUF; pour OpenGL, le double tampon est par défaut dans l'implémentation courante de SDL mais je trouve important de rajouter cet appel). Enfin, on utilise la fonction SDL_SetVideoMode pour dire qu'on veut utiliser SDL_OPENGL. C'est ici qu'il y a un changement par rapport à un programme pur SDL.

Ensuite, nous avons le code d'initialisation OpenGL. Puisque nous sommes en train de faire un jeu 2D, la mise en place d'une vue orthogonale est logique. Voici comment on la met en place :

Initialisation d'OpenGL
Sélectionnez

    //Remettre a zero la matrice de projection
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    // Mettre les matrices en place pour une vue orthogonale
    glOrtho(0,WIDTH,HEIGHT,0,-1,1);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

Nous allons commencer par remettre à zéro la matrice de projection. Ensuite, nous appelons glOrtho pour avoir une vue orthogonale. Les deux premiers paramètres sont les bornes de la largeur de la fenêtre. Les deux paramètres qui suivent sont les bornes de la hauteur de la fenêtre. Enfin, les deux derniers paramètres définissent les bornes de profondeur. Ici, WIDTH et HEIGHT sont définies dans un .h comme ceci :

Définition des variables
Sélectionnez

const int WIDTH=800;
const int HEIGHT=600;

Avant de commencer la boucle événementielle, nous initialisons last et fps. Prenons donc le temps d'expliquer l'utilité de ces deux variables. fps servira pour compter le nombre d'images par seconde. A chaque itération de la boucle événementielle, nous allons regarder l'heure actuelle en utilisant la fonction SDL_GetTicks qui fournit l'heure en nombre de millisecondes (donc la division par 1000 permet d'avoir ce temps en secondes). Nous allons ensuite comparer l'heure actuelle avec la valeur de last. Si la valeur est différente, nous allons pouvoir calculer le nombre d'affichages par seconde.

Initialisation des variables last et fps
Sélectionnez

    last = SDL_GetTicks()/1000;
    fps = 0;
Boucle événementielle
Sélectionnez

    //Boucle generale
    while(!done)
    {
        //Traiter les evenements
    while(SDL_PollEvent(&event))
    {
        switch(event.type)
        {
            case SDL_QUIT:
                done=1;
                break;
              case SDL_KEYUP:
                if(event.key.keysym.sym==SDLK_q)
                    done=1;
                break;
            default:
                break;
        }
    }

Rien de spécial dans cette boucle. Nous parcourons les événements qui sont en attente. Si l'événement est de type SDL_QUIT, on met la variable done à jour. Si l'événement est de type SDL_KEYUP et que la touche relâchée est la lettre 'q' alors on met done à jour pour quitter le programme.

A la fin de la boucle événementielle, on incrémente la variable fps. Et on vérifie l'heure actuelle. Enfin, on appelle la fonction affiche pour appeler l'affichage de la scène. Je pense que le code qui suit est assez explicite :

Fin de la boucle événementielle
Sélectionnez

    fps++;

    now = SDL_GetTicks()/1000;
    if(now!=last)
        {
        cout << "FPS: " << fps/(now-last) << endl;
        last = now;
        fps = 0;
        }

    affiche();
    }

La fin du programme se termine l'appel SDL_Quit.

Fin du programme
Sélectionnez

    SDL_Quit();
    return EXIT_SUCCESS;
}

3. Conclusion▲

Voilà pour cette première partie, nous avons fait une introduction de l'utilisation des bibliothèques SDL et OpenGL.

Comme vous le voyez, il est assez facile d'ouvrir une fenêtre et, pour ajouter du code d'affichage, il suffit de compléter la fonction affiche.

Cette première partie n'est qu'un début et on me dira que c'est encore un autre tutoriel d'ouverture d'une fenêtre SDL. Mais je préfère prendre le temps de le refaire encore une fois, ne serait-ce que parce que l'ajout de la gestion OpenGL change tout de même certaines choses. Les parties qui vont suivre vont gérer l'affichage des balles, leurs collisions et la gestion de la souris.

Jc

Liens
  • Sommaire du tutoriel;
  • Introduction;
  • Les bases du moteur;
  • Les collisions et un menu;
  • Améliorer les collisions; Image non disponible
  • Le score, la souris et les joueurs;
  • Le réseau;
  • Conclusion.

4. Téléchargements▲

Voici le code source pour ce premier tutoriel: (3 Ko).

Voici la version pdf de cet article: (35 Ko).

Si vous avez des suggestions, remarques, critiques, si vous avez remarqué une erreur, ou bien si vous souhaitez des informations complémentaires, n'hésitez pas à me contacter !

5. Remerciements▲

J'aimerais remercier loka pour sa double relecture de cet article !

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2006 Jean Christophe Beyler. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -
  • Nous contacter
  • Participez
  • Hébergement
  • Informations légales

Copyright © 2000-2016 - www.developpez.com


Programmation de Jeux 2D: Un pong en SDL/OpenGL, Deuxième partie

Table des matières Plier Déplier

  • 1. Introduction
  • 2. La modification du fichier Main.cpp
    • 2.1. La classe Moteur
    • 2.2. Gérer la fréquence d'affichage
      • 2.2.a. Version basique
      • 2.2.b. La version SDL_gfx
  • 3. La classe Moteur
    • 3.1. Moteur.h
    • 3.2. Moteur.cpp
  • 4. La classe Jeu
    • 4.1. Le fichier Jeu.h
      • 4.1.a. Les champs membres
      • 4.1.b. Les fonctions membres
    • 4.2. Le fichier Jeu.cpp
      • 4.2.a. Le Constructeur et le Destructeur
      • 4.2.b. La fonction initTextures
      • 4.2.c. Les autres fonctions d'initialisation
      • 4.2.d. La fonction addObjet
      • 4.2.e. Les fonctions d'affichage
      • 4.2.f. La fonction updateObjets
      • 4.2.g. La fonction gereSceneServeur
  • 5. La classe Objet
    • 5.1. Le fichier Objet.h
      • 5.1.a. Les champs membres
      • 5.1.b. Le fichier Objet.cpp
  • 6. La classe Vecteur
  • 7. La classe Rand
  • 8. Conclusion
  • 9. Téléchargements
  • 10. Remerciements

Cette partie pose les fondations du jeu du pong. A travers 3 classes, nous allons pouvoir définir les interactions nécessaires pour la gestion d'un jeu comme le pong. Une classe Objet pour chaque balle (et bientôt chaque raquette), une classe pour le jeu et une classe Moteur pour diriger le tout. Cet article présente donc ces classes.

Bienvenue à cette deuxième partie sur la programmation du pong. Nous allons de nouveau ajouter beaucoup de choses dans cette partie mais rien de très surprenant. Si vous avez suivi la série du morpion, vous avez vu comment faire une classe Objet, une classe Jeu et une classe Moteur.

Cette partie présentera donc ces trois classes et leur interaction dans ce programme. Il y a beaucoup de choses à montrer donc je m'excuse déjà pour la longueur de cet article.

2. La modification du fichier Main.cpp▲

Il n'y a pas beaucoup de changements dans ce fichier. Mais nous allons présenter les quelques endroits où il y a des différences dans cette section.

2.1. La classe Moteur▲

Dans ce fichier Main.cpp, nous déclarons une variable globale :

Déclaration d'une variable globale
Sélectionnez

//Variable globale
Moteur moteur;

Comme vous le voyez, le programme aura une instance de la classe Moteur. Cette classe permettra de faire le lien entre le jeu et la fonction main. Nous verrons la déclaration de cette classe plus loin dans cet article.

Nous avons un appel à une fonction init de la classe moteur. Cet appel permettra de gérer toute l'initialisation du moteur.

Initialisation de la classe moteur
Sélectionnez

//Initialisation du moteur
if(moteur.init()==false)
    done = 1;

2.2. Gérer la fréquence d'affichage▲

2.2.a. Version basique▲

La bibliothèque SDL ne contient pas directement des fonctions pour gérer la fréquence d'affichage. En suivant la réponse de la question FAQ Comment gérer la vitesse d'affichage ?, nous allons mettre le code qui permet de limiter la fréquence d'affichage. Nous commencerons donc par initialiser la variable checkTime avant la boucle événementielle.

Initialisation de la variable checkTime
Sélectionnez

checkTime = SDL_GetTicks();

Ensuite, nous allons entourer le code d'affichage et le code de gestion de la variable fps avec le code de la FAQ :

Gestion de l'affichage
Sélectionnez

if(SDL_GetTicks() > (checkTime + 1000 / wantedfps) )
  {
  // On met a jour la variable checkTime
  checkTime = SDL_GetTicks();

  // On incremente la variable fps
  fps++;
  // Gerer l'affichage du fps
  now = time(NULL);
  if(now!=last)
    {
    cout << "FPS: " << fps/(now-last) << endl;
    last = now;
    fps = 0;
    }

  // Demander au moteur de dessiner la scene
  moteur.gereScene();
  }
else
  {
  // Attendre 5 millisecondes
  SDL_Delay(5);
  }

Comme vous le voyez, on ira dans le corps du if tous les 1000/wantedfps millisecondes. C'est maintenant à travers de la fonction membre gereScene que l'affichage et la gestion du jeu se fera.

Vous remarquerez que nous ajoutons une clause else. Ce code permet d'arrêter l'exécution pendant 5 millisecondes pour donner un peu de temps au système d'exploitation.

2.2.b. La version SDL_gfx▲

Si vous voulez une technique un plus facile à gérer, il vous faudra utiliser la bibliothèque SDL_gfx. En effet, cette extension permet d'avoir des fonctions en plus, dont celles qui vont nous servir ici. Les fonctions qui doivent être utilisées pour gérer la fréquence d'affichage sont définies dans le fichier d'en-tête SDL_framerate.h (par contre, utiliser l'option -lSDL_gfx pour la compilation).

Voici les fonctions définies dans ce fichier :

Les fonctions définies dans le fichier SDL_framerate.h
Sélectionnez

void SDL_initFramerate(FPSmanager * manager);
int SDL_setFramerate(FPSmanager * manager, int rate);
int SDL_getFramerate(FPSmanager * manager);
void SDL_framerateDelay(FPSmanager * manager);

Toutes ces fonctions utilisent un paramètre de type FPSmanager. Pour chaque programme, vous en définissez un (allouer statiquement ou dynamiquement) que vous passerez à chaque fonction. On va montrer maintenant comment on les utilise.

On commencera par définir une variable de type FPSmanager. Ensuite, en appelant SDL_initFramerate, on initialise la structure.

Définition et initialisation de FPSmanager
Sélectionnez

FPSmanager manager;

//Initialisation
SDL_initFramerate(&manager);

Par défaut, l'utilisation des ces fonctions mettent en place 30 images par seconde. Pour définir un autre taux d'affichage, il faut utiliser la fonction SDL_setFramerate. Pour récupérer le taux d'affichage, on utilise SDL_getFramerate.

Mettre 60 images par seconde
Sélectionnez

//Mettre le nombre d'images par secondes souhaite
SDL_setFramerate(&manager,35);

Bien sûr, si le programme n'arrive pas à garder un taux d'affichage aussi élevé, cette technique (comme la version naïve) ne pourra pas faire mieux. Il faut donc s'assurer que le code de rendu est assez rapide.

Enfin, pour mettre en place la gestion du taux d'affichage, on fait un appel à SDL_framerateDelay. Vous devez faire un appel pour chaque itération de la boucle globale. Cela ressemblerait donc à :

Mettre 60 images par seconde
Sélectionnez

while(!done)
{
  ...
  // Demander au moteur de dessiner la scene
  moteur.gereScene();
  SDL_framerateDelay(&manager);
}

3. La classe Moteur▲

Nous allons donc présenter la classe Moteur. Ceci sera fait en deux parties, d'abord le fichier d'en-tête puis le fichier source.

3.1. Moteur.h▲

Déclaration de la classe
Sélectionnez

class Moteur
{
  private:
    // Le jeu
    Jeu* jeu;

    // Sommes-nous dans le menu ?
    bool dans_menu;

    // Dessiner le jeu
    void dessineJeu();
    // Dessiner le menu
    void dessineMenu();

  public:
    // Le constructeur et destructeur
    Moteur();
    ~Moteur();

    // Gerer la scene (affichage + mise a jour)
    void gereScene();

    // Fonction d'initialisation
    bool init();
};

Comme vous le voyez, dans cette version du moteur du jeu, nous avons deux variables.

  • Nous avons une instance jeu de la classe Jeu. Comme vous le pouvez le voir, cette classe Moteur ne posséde rien de plus sur le jeu sous-jacent. Ceci est logique. Nous voulons garder cette classe aussi indépendante que possible.
  • Nous avons aussi un booléen menu qui permettra de savoir si nous sommes dans le menu ou dans le jeu. Bien que dans cette version, le menu ne sera pas implémenté, je pensais que ce serait bien de le mettre tout de suite en place.

Cette classe posséde aussi 4 fonctions membres (dont 2 sont en private):

  • gereScene : cette fonction est le point d'entrée pour l'affichage. Dépendant de la valeur du booléen menu on pourra appeler dessineJeu ou dessineMenu;
  • dessineJeu : cette fonction se charge de l'affichage lorsque le programme est en mode jeu;
  • dessineMenu : cette fonction se charge de l'affichage lorsque le programme est en mode menu;
  • init : cette fonction gére l'initialisation de cette classe et de la classe init.

3.2. Moteur.cpp▲

Cette classe n'est qu'à ses débuts, elle est assez vide pour le moment! Mais nous allons tout de même présenter les fonctions de cette classe.

Constructeur et Destructeur
Sélectionnez

//Constructeur
Moteur::Moteur()
{
  menu = false;
  jeu = new Jeu();
}

//Destructeur
Moteur::~Moteur()
{
  delete jeu;
}

Comme vous le voyez, la seule initialisation est de mettre la variable menu à false et allouer de la mémoire pour l'instance jeu. Le programme ira donc directement dans le mode jeu comme nous pouvons le voir dans la fonction gereScene :

La fonction gereScene
Sélectionnez

void Moteur::gereScene()
{
  // Effacer le tampon des couleurs
  glClear(GL_COLOR_BUFFER_BIT);
  glLoadIdentity();

  if(!menu)
    dessineJeu();
  else
    dessineMenu();

  SDL_GL_SwapBuffers();
}

Ensuite, viennent le tour des fonctions dessineMenu et dessineJeu. Pour le moment, la fonction dessineMenu est vide. Par contre, le code de la fonction dessineJeu contient les appels pour la classe jeu.

Les fonctions dessineMenu et dessineJeu
Sélectionnez

void Moteur::dessineMenu()
{
}

void Moteur::dessineJeu()
{
  jeu->affiche();
  jeu->gereSceneServeur();
}

A quoi sert la fonction gereScene ? Pour pouvoir ajouter des balles, mettre à jour les positions des balles, vérifier les collisions, ...

Pourquoi ne pas mettre ce code dans la fonction d'affichage ? C'est en effet une chose souvent faite. A chaque affichage, on met à jour la position des objets. Mais ce n'est pas très portable. Par exemple, un client du jeu a juste besoin d'avoir les positions des objets et les afficher. Le client n'a pas besoin de mettre à jour les objets, c'est le travail du serveur (bien sûr, dans certaines applications le client fait aussi une partie du travail).

Finalement, nous avons la fonction d'initialisation du moteur :

La fonction init
Sélectionnez

bool Moteur::init()
{
   return jeu->init();
}

Cette fonction appelle simplement la fonction d'initialisation pour l'instance du jeu et retourne son résultat.

4. La classe Jeu▲

La classe Jeu posséde toutes les informations du jeu. Nous allons présenter la classe Jeu dans cette section.

4.1. Le fichier Jeu.h▲

4.1.a. Les champs membres▲

Nous allons commencer par la présentation des champs membres de la classe Jeu :

Les membres de la classe du Jeu
Sélectionnez

  //Les objets
  std::vector<Objet> objets;

  //Pour la creation des balles
  unsigned int last;

Comme vous le voyez, nous avons un vecteur objets pour les objets définis par une constante (qui se trouvera dans le fichier Define.h). Le type vector nous permet bien sûr de pouvoir facilement ajouter des objets dans le jeu et aussi d'en enlever sans difficulté.

On utilise la variable last pour savoir quand ajouter une nouvelle balle dans la partie. Nous verrons plus en détail son utilité lors de la prochaine section.

4.1.b. Les fonctions membres▲

Voici les fonctions membres de la classe Jeu :

Les fonctions membres
Sélectionnez

private:
    ...

    //Initialisation des objets et des textures
    bool initObjets();
    bool initTextures();

public:
    //Constructeur et Destructeur
    Jeu();
    ~Jeu();

    //Initialisation de la classe
    bool init();

    //Ajouter un objet dans le jeu, retourne l'indice de l'objet ajoute
    int addObjet(Objet &o);
    //Retourner le nombre d'objets dans le jeu
    unsigned int getNObjets();

    //Afficher le jeu
    void affiche();
    void affObjets();

    //Mettre a jour les objets
    void updateObjets();

    //Gere la scene (mise a jour)
    void gereSceneServeur();

Nous avons mis les deux fonctions initObjets et initTextures en privé puisque ce sont des fonctions qui devraient être uniquement appelées par la fonction init

Je ne pense pas que ce soit nécessaire d'en dire plus sur ces fonctions, les commentaires doivent suffire.

4.2. Le fichier Jeu.cpp▲

Montrons l'implémentation de la classe Jeu. A part la fonction initTextures, il n'y a pas grand chose de difficile dans cette classe mais je pense que c'est important de présenter les grands points.

4.2.a. Le Constructeur et le Destructeur▲

Constructeur et Destructeur
Sélectionnez

//Constructeur
Jeu::Jeu()
{
    last = 0;
}

//Destructeur
Jeu::~Jeu()
{
}

Comme vous le voyez le constructeur met simplement à zéro les variables last.

4.2.b. La fonction initTextures▲

La fonction initTextures sert à initialiser les textures. Elle retourne true si tout s'est bien passé et false s'il y a eu un problème. Voici le code de cette fonction :

La fonction initTextures
Sélectionnez

bool Jeu::initTextures()
{
  int i,j,cnt,pcnt;
  GLuint txtballe;

  unsigned char *pixels, *tmppixels;

  //On charge l'image "data/balle.bmp"
  SDL_Surface *tmpballe = SDL_LoadBMP("data/balle.bmp");
  if(tmpballe==NULL)
    {
    std::cerr << "Erreur dans le chargement de l'image de la balle" << std::endl;
    return false;
    }

  //On cree une surface RGBA de meme dimension
  SDL_Surface *balle = SDL_CreateRGBSurface(SDL_SWSURFACE, tmpballe->w, tmpballe->h, 32,
                0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000);
  if(balle==NULL)
    {
    std::cerr << "Erreur dans la creation de l'image de la balle" << std::endl;
    return false;
    }

Pour dessiner les balles du jeu, nous allons utiliser une texture OpenGL. Ces textures sont définies par une variable de type GLuint. Pour notre jeu et pour nos balles, nous utilisons une variable nommée txtballe.

On commence donc cette fonction par le chargement de l'image d'une balle et de la création d'une surface de même dimension. Nous avons besoin de cette nouvelle surface pour mettre en place le canal alpha. En effet, si vous vous souvenez de l'image d'une balle :

Image non disponible
La balle

Cette image est carré mais nous voulons une image ronde lors du rendu. Comment faire ? La réponse est le canal alpha. Rappelons, qu'avec la bibliothèque SDL, on utilise la fonction SDL_SetColorKey. Cette fonction permet de définir la couleur transparente de l'image.

Sous OpenGL, on utilise la valeur du canal alpha pour définir quel pixel sera dessiné ou non. Voyons la suite de cette fonction. Pour regarder et modifier les pixels des surfaces, il suffit d'utiliser les pointeurs pixels et tmppixels.

La suite de la fonction
Sélectionnez

  pixels = (unsigned char*) balle->pixels;
  tmppixels = (unsigned char*) tmpballe->pixels;

  pcnt = 0;
  cnt = 0;

  //Pour chaque pixel
  for(i=0;i<tmpballe->w;i++)
    {
    for(j=0;j<tmpballe->h;j++)
      {
      //On copie la couleur
      pixels[pcnt] = tmppixels[cnt];
      pixels[pcnt+1] = tmppixels[cnt+1];
      pixels[pcnt+2] = tmppixels[cnt+2];

      //La couleur magenta sera transparente
      if((tmppixels[cnt]==255)&&(tmppixels[cnt+1]==0)&&(tmppixels[cnt+2]==255))
        {
        pixels[pcnt+3] = 255;
        }
      else
        {
        pixels[pcnt+3] = 0;
        }

      cnt += 3;
      pcnt += 4;
      }
    }

Je ne pense pas que ce code soit très difficile à comprendre. On parcourt chaque pixel, si la couleur est magenta, on met le canal alpha à 255 sinon il est mis à 0 (remarquez que normalement on utilise une couleur moins utilisée comme le magenta mais c'est un choix arbitraire).

La seule chose dont il faut faire attention, c'est l'incrémentation de 3 pour le compteur cnt et 4 pour le compteur pcnt. En effet, pour l'image source on a que trois canaux (donc trois octets par pixel) et pour l'image destination on en a quatre (donc quatre octets par pixel).

La suite de cette fonction gére simplement le chargement de la surface sous forme de texture OpenGL. Nous commencerons par générer un indice pour la texture. Ensuite, nous utiliserons la fonction glBindTexture pour dire que c'est cette texture que nous voulons modifier. Les fonctions glTexParameteri permettent de dire quels filtres nous allons utiliser (voir cette partie pour plus de détail). Enfin, la fonction glTexImage2D permet de vraiment passer le tableau de l'image. Voici le code associé :

Chargement de l'image pour OpenGL
Sélectionnez

    //On passe la texture a OpenGL

    //Generation d'un indice de texture
    glGenTextures(1,&txtballe);

    //On choisit la texture et on definit un simple filtre pour la texture
    glBindTexture(GL_TEXTURE_2D,txtballe);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);

    //Chargement de l'image
    glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,pion->w,pion->h,0,
          GL_RGBA,GL_UNSIGNED_BYTE,balle->pixels);

    //Liberation des surfaces
    SDL_FreeSurface(balle);
    SDL_FreeSurface(tmpballe);

Avant de terminer cette fonction, on va passer à la classe Objet l'indice de la texture générée.

Passer l'indice à la classe Objet
Sélectionnez

    //On met a jour la variable statique txtballe de la classe Objet
    Objet::setTxtBalle(txtballe);

    return true;
}

4.2.c. Les autres fonctions d'initialisation▲

Rien de bien particulier pour ces deux autres fonctions d'initialisation. En effet, la fonction init permet d'appeler les deux autres fonctions (initTextures que nous avons déjà montré et initObjets qui remet le nombre d'objets à zéro en utilisant la fonction clear de la classe vector.

Fonctions d'initialisation
Sélectionnez

//Initialisation du jeu
bool Jeu::init()
{
    return initObjets() && initTextures();
}

bool Jeu::initObjets()
{
    objets.clear();
    return true;
}

4.2.d. La fonction addObjet▲

La fonction addObjet permet d'ajouter une balle dans le jeu s'il y a de la place dans le tableau. Cette fonction retourne -1 s'il y a eu un problème ou l'indice de l'objet qui vient d'être ajouté si tout s'est bien passé.

La fonction addObjet
Sélectionnez

int Jeu::addObjet(Objet &o)
{
  // Si on a encore de la place
  if(getNObjets()<MAX_BALLES)
    {
    // On copie l'objet dans la case vide
    objets.push_back(o);
    //On rend l'indice de l'objet insere
    return getNObjets()-1;
    }
  return -1;
}

Cette fonction appelle la fonction getNObjets qui retourne simplement le nombre d'éléments dans le vecteur objets :

La fonction getNObjets
Sélectionnez

inline unsigned int Jeu::getNObjets()
{
   return objets.size();
}

4.2.e. Les fonctions d'affichage▲

Les fonctions d'affichage pour la classe Jeu se divisent en deux fonctions. La fonction affiche est le point d'entrée pour l'affichage du jeu. Pour le moment, elle appelle simplement la fonction affObjets.

Fonction d'affichage
Sélectionnez

void Jeu::affiche()
{
  //Afficher les objets
  affObjets();
}

void Jeu::affObjets()
{
  int i;

  //On met en place le test sur la valeur alpha
  glEnable(GL_ALPHA_TEST);
  //On dessine si la valeur alpha est inférieur
  glAlphaFunc(GL_LEQUAL,0.1f);

  //On affiche les objets
  for(i=0;i<getNObjets();i++)
    {
    objets[i].affiche();
    }

  //On desactive le test sur la valeur alpha
  glDisable(GL_ALPHA_TEST);
}

La fonction glEnable permet de mettre en place le test Alpha. La fonction glAlphaFunc permet de définir comment on gére le canal alpha. En résumé, on définit une valeur seuil (0.1f dans ce cas) et avec GL_LEQUAL, on définit qu'on dessine le pixel si le canal alpha est inférieur à 0.1f. Si vous vous souvenez de la fonction initTextures, on a mis 1 pour les pixels magentas et 0 pour les autres, donc les pixels qui ne sont pas de la couleur magenta seront dessinés.

La fonction affObjets permet donc d'afficher tous les objets. Comme vous le voyez, nous entourons la boucle d'instructions permettant de tester le canal alpha. Enfin, vous remarquerez que, comme pour la série sur le morpion, c'est à chaque objet de gérer son affichage.

4.2.f. La fonction updateObjets▲

La fonction updateObjets
Sélectionnez

void Jeu::updateObjets()
{
  int i;

  // On met à jour chaque objet
  for(i=0;i<getNObjets();i++)
    {
    objets[i].updatePos();
    }
}

Comme pour la fonction d'affichage, cette fonction est le point d'entrée pour la mise à jour des positions des objets. Comme vous le voyez, on parcourt simplement les objets et on appelle la fonction pour mettre à jour leur position.

4.2.g. La fonction gereSceneServeur▲

La fonction gereSceneServeur est la dernière fonction que nous allons présenter ici. C'est une fonction qui va appeler la fonction updateObjets et ensuite regarder s'il est temps d'ajouter une nouvelle balle.

La fonction gereSceneServeur
Sélectionnez

void Jeu::gereSceneServeur()
{
  int vx,vy;
  int r,g,b;
  Objet tmpobj;

  // On met à jour les objets
  updateObjets();

  // Si on doit ajouter un objet
  if(SDL_GetTicks()>last+1000)
    {
    last = SDL_GetTicks();

    // Definit la position du nouvel objet (milieu de l'ecran)
    tmpobj.setPos(WIDTH/2-TAILLE_BALLE, HEIGHT/2-TAILLE_BALLE);

    // Choisit aleatoirement une direction (refusant un 0 pour vx ou vy)
    vx = Rand::randi(5000) - 2500;
    if(vx == 0)
      vx++;
    vy = Rand::randi(5000) - 2500;
    if(vy == 0)
      vy++;
    tmpobj.setDirVitesse(vx,vy);

    // On definit une norme de 4 pour la vitesse
    tmpobj.setVitesse(4);
    // On definit la taille
    tmpobj.setTaille(TAILLE_BALLE,TAILLE_BALLE);

    // On choisit une couleur avec un minimum de 50 par couleur
    r = Rand::randi(205)+50;
    g = Rand::randi(205)+50;
    b = Rand::randi(205)+50;

    tmpobj.setCouleur(r/256.0f, g/256.0, b/256.0);

    // On ajoute l'objet
    addObjet(tmpobj);
    }
}

Comme vous le voyez, on met à jour la position des objets et ensuite on regarde s'il est temps d'ajouter un objet dans le jeu. Si c'est le cas, on crée un objet au milieu de l'écran et on choisit une direction pour la vitesse aléatoirement (par contre, on définit la norme comme étant égale à 4). Ensuite, on choisit aussi une couleur aléatoire. Enfin, on ajoute l'objet dans le jeu.

Pour ne pas avoir trop de balles dans la fenêtre, on définit une variable MAX_BALLES qui permet de limiter le nombre.

Je pense que les noms des fonctions de la classe Objet sont assez clairs mais je vais prendre deux phrases pour les expliquer. setDirVitesse permet de définir la direction de la balle. setVitesse, setTaille et setCouleur permettent respectivement de définir la vitesse, la taille et la couleur de la balle.

5. La classe Objet▲

Nous avons déjà présenté la classe Moteur et la classe Jeu. Nous allons maintenant prendre un moment pour présenter la classe Objet.

5.1. Le fichier Objet.h▲

La classe Objet est la base de tout le programme. En effet, c'est la plus petite entité dans le jeu (remarquez que dans cette partie, on parlera encore de classes Vecteur et Rand mais ce ne sont que des classes pour aider la définition des autres, pas des classes pour le jeu).

5.1.a. Les champs membres▲

Les champs membres de la classe Objet sont définis :

Les membres de la classe Objet
Sélectionnez

  static GLuint txtballe;

  // Position, vitesse et taille de l'objet
  Vecteur pos,
    vitdir,
    taille;

  // Couleur
  float r,g,b;

Comme vous le voyez, il n'y a pas grand chose de spécial dans cette classe. Nous avons une variable statique pour l'indice de la texture pour les balles. Ensuite, nous avons un vecteur pour la position, la direction de la balle et la taille de la balle. Enfin, nous avons trois variables pour la couleur.

Nous n'allons pas présenté chaque fonction de cette classe (il y en a beaucoup pour chaque élément qui doit être accédé ou modifié. Nous allons présenter ceux qui ont un intêrét :

Les fonctions membres
Sélectionnez

  static void setTxtBalle(GLuint t);

  void setPos(double, double);
  void setTaille(double, double);
  void setVitesse(double);
  void setCouleur(float,float,float);

  //Fonction d'affichage
  void affiche();

  //Fonction de gestion  de collision
  void updatePos();

La première fonction setTxtBalle est une fonction statique pour définir la valeur du champs statique txtBalle. Ensuite, nous avons quatre fonctions pour définir la position, la taille, la vitesse et la couleur de la balle.

Ensuite nous avons une fonction pour afficher la balle et une pour mettre à jour la position.

5.1.b. Le fichier Objet.cpp▲

Le code des fonctions de la classe Objet est assez simple, on va montrer les fonctions les unes après les autres. On commence par ne pas oublier de mettre la variable statique txtballe à zéro.

La variable statique
Sélectionnez

//Variable statique
GLuint Objet::txtballe = 0;

Ensuite nous avons la fonction qui définit la valeur de cette variable statique :

La fonction setTxtBalle
Sélectionnez

//Mise en place de la texture d'une balle
void Objet::setTxtBalle(GLuint t)
{
    txtballe = t;
}

Nous allons maintenant présenter les fonctions set :

Les fonctions set
Sélectionnez

//Mise en place de la position de l'objet
void Objet::setPos(double x1, double y1)
{
    pos.setX(x1);
    pos.setY(y1);
}

//Mise en place de la direction de l'objet
void Objet::setDirVitesse(double vx1, double vy1)
{
    vitdir.setX(vx1);
    vitdir.setY(vy1);
}

//Mise en place de la taille de l'objet
void Objet::setTaille(double w1, double h1)
{
    taille.setX(w1);
    taille.setY(h1);
}

Comme vous le remarquez, ces fonctions ne font que passer l'information aux variables de type Vecteur.

Il reste la fonction d'affichage et la fonction de mise à jour de la position. Tout d'abord, nous allons voir la fonction d'affichage :

La fonction affiche
Sélectionnez

// Fonction d'affichage
void Objet::affiche()
{
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, txtballe);

    glColor3u(r,g,b);
    glBegin(GL_QUADS);
        glTexCoord2i(0,1);
        glVertex3f(pos.getX(),pos.getY(),0);               // En haut a gauche
        glTexCoord2i(1,1);
        glVertex3f(pos.getX()+taille.getX(),pos.getY(),0); // En haut a droite
        glTexCoord2i(1,0);
        glVertex3f(pos.getX()+taille.getX(),pos.getY()+taille.getY(),0); // En bas a droite
        glTexCoord2i(0,0);
        glVertex3f(pos.getX(),pos.getY()+taille.getY(),0); // En bas a gauche
    glEnd();
    glDisable(GL_TEXTURE_2D);
}

Le code est assez clair, on met en place l'utilisation des textures. On dit qu'on veut utiliser la texture des balles. On définit la couleur de cette balle et on dessine le quadrilatère de la balle.

La dernière fonction que nous allons présenter de cette classe Objet est la fonction updatePos. En utilisant la position courante et la vitesse, on peut calculer la nouvelle position. Une fois mise en place, on va tester pour voir si la balle est en train de sortir de l'écran, si c'est le cas, on la remet au bord et on modifie la vitesse pour la faire repartir dans la direction opposée.

La fonction updatePos
Sélectionnez

//Fonction de mise a jour
void Objet::updatePos()
{
    //Calcul de la nouvelle position
    double x = pos.getX() + vitdir.getX(),
           y = pos.getY() + vitdir.getY(),
           w = taille.getX(),
           h = taille.getY();

    //Mise a jour
    pos.set(x,y);

    //Verification de la position ie si on sort de l'ecran on "rebondit"
    //A gauche
    if(x<0)
        {
        pos.setX(1);
        if(vitdir.getX()<0)
            vitdir.setX(vitdir.getX()*(-1));

        }

    //A droite
    if(x+w>=WIDTH)
        {
        pos.setX(WIDTH-w-1);
        if(vitdir.getX()>0)
            vitdir.setX(vitdir.getX()*(-1));
        }

    //En haut
    if(y<0)
        {
        pos.setY(1);
        if(vitdir.getY()<0)
            vitdir.setY(vitdir.getY()*(-1));
        }

    //En bas
    if(y+h>=HEIGHT)
        {
        pos.setY(HEIGHT-h-1);
        if(vitdir.getY()>0)
            vitdir.setY(vitdir.getY()*(-1));
        }
}

Vous remarquerez le test sur la positivité de la direction. Cela permet d'être sûr qu'on modifie correctement la vitesse lorsque c'est nécessaire.

6. La classe Vecteur▲

La classe Vecteur est relativement simple, nous présenterons simplement la définition de la classe.

La classe Vecteur
Sélectionnez

class Vecteur
{
    private:
        double x, y;

    public:
        Vecteur();
        ~Vecteur();

        void set(double, double);
        void setX(double x);
        void setY(double y);
        void normalise();

        void setNormalise(double, double);

        double getX();
        double getY();
};

Je ne pense pas que ce soit la peine d'expliquer plus le code. Cette classe a deux membres x et y et les fonctions permettent de définir la valeur de ces membres. Les seules fonctions intéressantes sont normalise qui permet de normaliser le vecteur et la fonction setNormalise qui permet de mettre en place le vecteur placé en paramètre et ensuite on normalise le vecteur.

7. La classe Rand▲

La dernière classe que nous allons présenter est la même que celle qui a été présentée dans la série morpion. Regardez ici pour plus de détails.

8. Conclusion▲

Voilà, la deuxième partie d'une série est toujours la plus longue puisqu'on doit prendre le temps de bien présenter toutes les classes qui sont en jeu. Voici une image de ce que nous venons de faire :

Image non disponible
Une image du programme actuel

La classe Moteur permet de faire le lien entre les informations données par la fonction main (et donc la bibliothèque SDL) et le jeu sous-jacent. La classe Jeu permet de définir et de gérer la cohérence entre tous les objets sous-jacents. Par exemple, le moteur va dire au jeu d'afficher tous les objets.

Bien sûr chaque objet possède aussi son propre indépendance. Ils mettent à jour leur position, savent s'afficher à l'écran.

Ceci conclut donc cette partie du tutoriel. Nous allons montrer comment gérer simplement les collisions dans la prochaine partie.

Jc

Liens
  • Sommaire du tutoriel;
  • Introduction;
  • Les bases du moteur;
  • Les collisions et un menu;
  • Améliorer les collisions; Image non disponible
  • Le score, la souris et les joueurs;
  • Le réseau;
  • Conclusion.

9. Téléchargements▲

Voici le code source pour ce tutoriel: (15.6 Ko) (version naïve), (15.6 Ko) (version SDL_framerate).

Voici la version pdf de cet article: (115 Ko).

Si vous avez des suggestions, remarques, critiques, si vous avez remarqué une erreur, ou bien si vous souhaitez des informations complémentaires, n'hésitez pas à me contacter !

10. Remerciements▲

J'aimerais remercier loka pour sa double relecture de cet article !

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2006 Jean Christophe Beyler. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -
  • Nous contacter
  • Participez
  • Hébergement
  • Informations légales

Copyright © 2000-2016 - www.developpez.com


Programmation de Jeux 2D: Un pong en SDL/OpenGL, Troisième partie

Table des matières Plier Déplier

  • 1. Introduction
  • 2. Les collisions
    • 2.1. Classe Physique
    • 2.2. Collision Cercle-Cercle
    • 2.3. Collision Rectangle-Rectangle
    • 2.4. La gestion des collisions entre les objets
    • 2.5. La fonction updateObjets
    • 2.6. Appel au moteur physique
    • 2.7. L'insertion des balles
  • 3. Le menu
    • 3.1. Les raccourcis du clavier
    • 3.2. Le bouton Continuer
  • 4. La classe Jeu
  • 5. La classe Moteur
  • 6. Conclusion
  • 7. Téléchargements
  • 8. Remerciements

Dans cette partie, nous allons nous intéresser aux collisions des balles et à la création d'un menu. Il existe beaucoup de solutions pour cette gestion mais nous allons voir ici une solution simple et facile à mettre en place. Nous allons présenter une classe Physique. Elle sera responsable pour le déplacement des objets, mais aussi des collisions entre les objets. Enfin, nous présenterons une solution rapide pour mettre en place un menu avec des raccourcis claviers.

Bienvenue à cette troisième partie sur la programmation du pong. Dans cette partie, nous allons nous intéresser à la gestion des collisions. La solution proposée ici est assez simple pour être facilement mise en place.

Cette partie présentera comment gérer les collisions et on introduira un menu dans l'application.

2. Les collisions ▲

Nous allons présenter dans cette section la gestion de collisions entre les objets. Ceci va se faire en intégrant une nouvelle classe Physique.

La classe Physique sera entièrement composée de fonctions statiques. Il existe deux fonctions publiques :

  • updateObjets : cette fonction met à jour la position et la vitesse des objets (en tenant compte bien sûr des collisions)
  • isCollision : cette fonction permet de vérifier s'il y a une collision avec le rectangle (x,y,w,h)

Enfin, voici la déclaration de la classe :

La classe Physique
Sélectionnez

class Physique
{
        public :
                //Fonction qui met à jour les objets
                static void updateObjets(std::vector<Objet> &objets);

                //Fonction qui teste la collision entre les objets et le rectangle (x,y,w,h)
                static bool isCollision(std::vector<Objet> &objets,
                            double x, double y, double w, double h);

        private :
                //Fonction qui gere la collision avec l'objet i
                static int collisionObjet(std::vector<Objet> &objets, int i);
                //Fonction qui teste la collision entre deux rectangles
                static bool collisionRect(double x1,double y1,double w1, double h1,
                            double x2, double y2,double w2,double h2);
                //Fonction qui teste la collision entre deux cercles
                static bool collisionCercle(double cx1,double cy1,
                            double r1, double cx2, double cy2, double r2);
};

Nous allons maintenant voir les fonctions qui gére la collision cercle-cercle et rectangle-rectangle.

2.2. Collision Cercle-Cercle▲

La collision entre deux cercles est plus facile à gérer. En effet, il suffit de calculer la distance entre deux cercles et voir si elle est inférieure au rayon d'un des cercles. Ce type de collision va être utilisé pour la collision entre deux balles.

Collision Cercle-Cercle
Sélectionnez

//Collision entre deux cercles (cx1,cy1,r1) et (cx2,cy2,r2)
bool Physique::collisionCercle(double cx1, double cy1, double r1,
                                 double cx2, double cy2, double r2)
{
   //Distance entre les deux centres
   double d = (cx1-cx2)*(cx1-cx2) + (cy1-cy2)*(cy1-cy2);
   d = sqrt(d);
   return (d<r1+r2);
}

2.3. Collision Rectangle-Rectangle▲

Il se peut que notre programme ait besoin d'une gestion de collision entre deux rectangles. Dans notre cas, nous aurons besoin de ce type de collision pour gérer la collision entre les balles et la raquette du jeu.

Collisions entre deux rectangles
Sélectionnez

//Fonctions qui gerent les collisions
bool Physique::collisionRect(double x1, double y1, double w1, double h1,
                        double x2, double y2, double w2, double h2)
{
    if(x1+w1<x2) return false;
    if(x2+w2<x1) return false;
    if(y1+h1<y2) return false;
    if(y2+h2<y1) return false;

    return true;
}

Comme vous le voyez, ce n'est pas très compliqué. La question est de savoir si le rectangle (x1,y1,w1,h1) est en collision avec (x2,y2,w2,h2).

On commence par regarder si l'abscisse x2 est à gauche de x1+w1. Si c'est le cas, il peut y avoir une intersection si x1 n'est pas à droite de x2+w2. En effet, cela voudrait dire que soit x2 appartient à l'intervalle [x1, x1+w1], soit x1 appartient à l'intervalle [x2,x2+w2]. Dans ce cas, on regarde si y1 est inclu dans [y2, y2+h2] ou si y2 est inclu dans [y1, y1+h1].

En faisant un dessin, je pense que vous arriveriez à voir tous les cas possibles et voir que nous les gérons tous... Ce type de collision sera utilisé par la collision entre une balle et une raquette.

2.4. La gestion des collisions entre les objets▲

Nous savons à présent gérer les collisions entre les balles et les collisions entre une balle et une raquette. Bien que nous n'aurons pas encore de raquette dans le programme actuel, nous allons préparer le terrain pour son introduction qui sera fait dans la prochaine partie.

On va poser comme hypothèse qu'avant la mise à jour des objets, aucun objet n'est en collision. Lorsque nous bougeons un objet, nous allons vérifier s'il y a eu une collision. Dans le cas d'une collision, nous remettons l'objet en place (donc à un endroit sans collision) et nous modifions sa direction.

Voici la fonction gérant les collisions :

Fonction testant la collision
Sélectionnez

//Est-ce que l'objet d'indice idx est en collision avec les autres ?
int Physique::collisionObjet(std::vector<Objet> &objets, int idx)
{
    int i;
    int nobjets = objets.size();

    double x1,x2,y1,y2;
    double w1,h1,w2,h2;

    double cx1=0,
           cy1=0,
           cr1=0,
           cx2=0,
           cy2=0,
           cr2=0;

    //On recupere la position et taille de l'objet idx
    x1 = objets[idx].getX();
    y1 = objets[idx].getY();
    w1 = objets[idx].getW();
    h1 = objets[idx].getH();

    //Pour le moment on n'a que des balles, on recupere son centre et rayon
    cx1 = x1+w1/2;
    cy1 = y1+h1/2;
    cr1 = w1/2;

    //On parcourt tous les autres objets
    for(i=0;i<nobjets;i++)
        {
        if(idx!=i)
            {
            x2 = objets[i].getX();
            y2 = objets[i].getY();
            w2 = objets[i].getW();
            h2 = objets[i].getH();

            //Recuperation du deuxieme centre et rayon
            cx2 = x2 + w2/2;
            cy2 = y2 + h2/2;
            cr2 = w2/2;

            //Si on a une collision entre les deux objets
            if(collisionCercle(cx1,cy1,cr1,cx2,cy2,cr2))
                return i;
            }
        }
    return -1;
}

Cette fonction n'est pas très compliquée. On commence par récupérer la position de l'objet en question. Puisque, pour le moment, nous avons que des balle, on utilise la position, la largeur et hauteur pour calculer le centre du cercle et son rayon.

Ensuite, on parcourt chaque objet et on regarde si on a une collision. Si les deux objets sont des cercles (donc notre cas pour le moment) alors nous devons tester pour une collision cercle-cercle.

Le retour de cette fonction représente l'objet qui est en collision ou -1. Ceci permettra au moteur physique de gérer la position des deux objets et leur vitesse.

2.5. La fonction updateObjets▲

La dernière fonction que nous allons présenter est nommée updateObjets. Cette fonction met à jour la position des objets du jeu. L'algorithme de la fonction met à jour la position de chaque objet puis vérifie s'il y a eu ou non une collision.

Dans le cas d'une collision, on remet l'objet dans sa position initiale et on met à jour la direction de l'objet. Voici l'implémentation de cette fonction, nous commencerons par présenter les déclarations des variables :

La fonction updateObjets
Sélectionnez

void Physique::updateObjets(std::vector<Objet> &objets)
{
        int i;
        int res;
        double oldx, oldy;

        double vx,vy,newvx, newvy,n;
        double x1,x2,y1,y2;

Ensuite, nous avons une simple boucle qui parcours chaque objet. Le programme sauvegarde la position de l'objet avant de la mettre à jour. Ensuite, on vérifie s'il y a eu une collision :

Mise a jour de l'objet
Sélectionnez

        for(i=0;i<nobjets;i++)
                {
                //Sauvegarde de l'ancienne position
                oldx = objets[i].getX();
                oldy = objets[i].getY();

                //Mise a jour de la position
                objets[i].updatePos();

                //Est-ce qu'on a une collision
                res = collisionObjet(objets, i);

Une fois le calcul de collision a été fait, on peut regarder la valeur de res pour une éventuelle collision. Si nous avons une collision, nous allons remettre la position de l'objet en place.

Remettre la position de l'objet
Sélectionnez

                if(res>=0)
                        {
                        //Si on a une collision, on remet l'ancienne position
                        objets[i].setPos(oldx,oldy);

Maintenant, le code le plus compliqué de ce tutoriel : la gestion de la collision. En effet, nous savons à présent qu'il y a eu une collision entre l'objet courant i et l'objet res. Il faut maintenant mettre à jour la direction des objets.

Gestion de la vitesse
Sélectionnez

                        //Recupere le centre de l'objet i
                        x1 = oldx + objets[i].getW()/2;
                        y1 = oldy + objets[i].getH()/2;

                        //Recupere le centre de l'objet res
                        x2 = objets[res].getX() + objets[res].getW()/2;
                        y2 = objets[res].getY() + objets[res].getH()/2;

                        //Calcul le vecteur de direction entre les centres
                        vx = x1 - x2;
                        vy = y1 - y2;

                        //Normalise le vecteur
                        n = vx*vx + vy*vy;
                        n = sqrt(n);

                        vx /= n;
                        vy /= n;

Comme vous le voyez, le programme commence par calculer le centre des deux objets. On calcule ensuite le vecteur normalisé qui représente la direction de l'objet res vers l'objet i. Le code qui suit vérifie que l'objet est un cercle (ce qui, pour cette demo, sera toujours le cas) et met la vitesse à jour.

Mettre à jour la vitesse des objets
Sélectionnez

                        //Mis a jour de l'objet i
                        newvx = vx;
                        newvy = vy;

                        objets[i].setDirVitesse(objets[i].getVX()+newvx,
                                           objets[i].getVY()+newvy);

                        //Mis a jour de l'objet res
                        newvx = (-vx);
                        newvy = (-vy);

                        objets[res].setDirVitesse(objets[res].getVX()+newvx,
                                           objets[res].getVY()+newvy);
                        }
                }
}

Vous remarquerez ma technique pour mettre à jour la vitesse de l'objet. Ce n'est pas politiquement correct mais je préfére garder le code simple. Une somme de vitesses suffira pour ce programme.

2.6. Appel au moteur physique▲

La fonction gereSceneServeur va faire un appel au moteur physique passant les informations des objets et du nombre d'objets.

Appel du moteur physique
Sélectionnez

//Mis a jour des objets
Physique::updateObjets(objets);

Comme vous le voyez, la position et de la vitesse des objets sont gérées par le moteur physique et plus directement par la classe Jeu. Ceci permet d'avoir une moteur physique indépendant des objets sous-jacents.

Enfin, voici une image de notre pong avec 50 balles:

Image non disponible
Une image du pong avec 50 balles, aucune balle n'est en collision

2.7. L'insertion des balles▲

La dernière chose à remarquer en rapport avec les collisions concerne l'insertion de nouvelles balles. En effet, pour être sûr qu'une balle puisse être inséré et que cela ne provoquera pas une collision, il suffit d'ajouter ceci dans la fonction gereSceneServeur de la classe Jeu.

Test de collision pour la nouvelle balle
Sélectionnez

// Si on doit ajouter un objet
if((SDL_GetTicks()>last+1000)
   &&(!Physique::isCollision(objets, WIDTH/2-TAILLE_BALLE,HEIGHT/2-TAILLE_BALLE,
        TAILLE_BALLE,TAILLE_BALLE)))

3. Le menu▲

Comme dans la série du morpion, nous allons mettre un menu en place. Certes, ce sera juste un menu avec deux boutons. Par contre, nous allons afficher le menu entièrement avec OpenGL pour ne pas mélanger le code d'affichage OpenGL et la bibliothéque SDL.

Cette petite différence est tout de même de taille puisqu'il faut gérer le chargement des fichiers images pour le menu et les faire passer en textures OpenGL. Ce détail nous oblige à changer les dimensions de nos images pour qu'elles soient égales à une puissance de deux.

Mais sinon, le code est presque le même, l'affichage du menu ne contient rien de spécial et le code du moteur ressemble énormément au code présenté dans la partie 5 de la série sur le morpion.

Voici une image de notre menu :

Image non disponible
Notre menu

3.1. Les raccourcis du clavier▲

Il y a tout de même un ajout à ce menu qui le rend nettement plus interactif que celui pour le morpion. Nous avons ajouté des raccourcis clavier (la touche 'n' pour Nouvelle partie et la touche 'q' pour Quitter.

Ceci se fait facilement avec une fonction clavier qui retourne si oui ou non la touche a servie.

Gestion du clavier
Sélectionnez

//Gestion du clavier
bool Menu::clavier(unsigned char k)
{
        switch(k)
        {
                case 'c':
                        if(moteur.jeuEnCours()) {
                            moteur.echangeFonctions();
                        }
                        return true;
                case 'n':
                        moteur.initJeu();
                        moteur.setFonctionsJeu();
                        return true;
                default:
                        return false;
        }
}

Et là on remarque qu'un raccourci n'est pas présent lors de l'affichage du menu ! Et nous avons un appel à une fonction jeuEnCours. Comme son nom l'indique nous avons une fonction membre de la classe Moteur qui permet de dire si le jeu est en cours. Si le jeu est en cours, nous avons en fait un bouton supplémentaire.

3.2. Le bouton Continuer▲

Ce bouton Continuer apparaît seulement lorsque la fonction membre jeuEnCours retourne vrai. En effet, si nous regardons la fonction d'affichage du menu, on remarque que l'affichage se fait en utilisant les fonctions OpenGL et donc rien ne devrait être surprenant :

Affichage du menu
Sélectionnez

//Fonction d'affichage
void Menu::affiche()
{
        int i;
        glEnable(GL_TEXTURE_2D);
        glDisable(GL_DEPTH_TEST);

        glAlphaFunc(GL_LESS,10);
        glEnable(GL_ALPHA_TEST);

        glColor3f(1.0,1.0,1.0);

        //On dessine les diffentes images
        for(i=0;i<4;i++)
                {
                glBindTexture(GL_TEXTURE_2D, images[i]);
                glBegin(GL_QUADS);
                        glTexCoord2i(0,0);glVertex2i(positions[i].x,positions[i].y);
                        glTexCoord2i(1,0);glVertex2i(positions[i].x+positions[i].w,positions[i].y);
                        glTexCoord2i(1,1);glVertex2i(positions[i].x+positions[i].w,positions[i].y+positions[i].h);
                        glTexCoord2i(0,1);glVertex2i(positions[i].x,positions[i].y+positions[i].h);
                glEnd();
                }

        //Pour le dernier bouton, cela dépend si le jeu est en cours
        //Remarquez i vaut 4, donc c'est bien
        if(moteur.jeuEnCours())
        {
                glBindTexture(GL_TEXTURE_2D, images[i]);
                glBegin(GL_QUADS);
                        glTexCoord2i(0,0);glVertex2i(positions[i].x,positions[i].y);
                        glTexCoord2i(1,0);glVertex2i(positions[i].x+positions[i].w,positions[i].y);
                        glTexCoord2i(1,1);glVertex2i(positions[i].x+positions[i].w,positions[i].y+positions[i].h);
                        glTexCoord2i(0,1);glVertex2i(positions[i].x,positions[i].y+positions[i].h);
                glEnd();
        }

        glDisable(GL_TEXTURE_2D);
}

Comme vous le voyez, nous avons encore fait un cas particulier pour l'affichage du bouton Continuer. Si le jeu est en cours, nous l'affichons, sinon il n'apparaît pas.

La fonction jeuEnCours appelle simplement la fonction enCours de l'instance du jeu. La classe Jeu posséde un booléen qui représente si une partie est en cours. Si c'est le cas, ce booléen vaut vrai sinon il vaut faux. Comme toujours, la classe Moteur sert de lien entre le menu et le jeu.

Image non disponible
Notre menu avec le nouveau bouton

4. La classe Jeu▲

Dans la classe Jeu, il n'y a pas eu beaucoup de changements par rapport au deuxième tutoriel. Un des grands changements est la perte de la mise à jour des objets. En effet, cela se fait maintenant lors de l'appel au moteur Physique.

Il y a toutefois un ajout, la gestion des raccourcis clavier :

Sélectionnez

//Gestion du clavier
bool Jeu::clavier(unsigned char k)
{
        switch(k)
        {
                //On veut voir le menu
                case 'q':
                        moteur.echangeFonctions();
                        return true;
                default:
                        return false;
        }
}bool Jeu::clavier(unsigned char k)
{
        switch(k)
        {
                //On veut voir le menu
                case 'q':
                        moteur.echangeFonctions();
                        return true;
                default:
                        return false;
        }
}

Comme nous le voyons ici, seule la touche 'q' sert pour passer du jeu au menu. Comme la fonction clavier de la classe Menu, cette fonction retourne un booléen exprimant si la touche a été gérée ou non. Ceci permettra au moteur de savoir s'il doit encore géré la touche (si nécessaire) ou non.

Cette méthode nous permet donc de définir des touches que le jeu sous-jacent (qu'on soit dans le menu ou dans le jeu) peut décider de gérer ou laisser au moteur leur comportement par défaut.

Par exemple, nous pouvons supposer que par défaut la touche 'q' permet de quitter le programme. Dans ce cas, si nous sommes dans le jeu, nous voulons d'abord passer par le menu. Donc nous ajoutons un cas 'q' dans la fonction clavier de la classe Jeu. En retournant true, la classe Jeu dit "C'est bon, j'ai géré la touche".

Pour continuer cet exemple, vous avez vu que dans le menu, nous ne gérons pas la touche 'q'. Nous faisons ceci parce que par défaut, si une touche 'q' est appuyé, le moteur quittera le programme si l'événement n'est pas géré par le menu ou le jeu. Voici le code qui gére le clavier dans la classe Moteur :

La gestion du clavier
Sélectionnez

void Moteur::clavier(unsigned char k)
{
        int gere=false;
        if(dans_menu)
        {
                gere = menu->clavier(k);
        }
        else
        {
                gere = jeu->clavier(k);
        }

        //Si la touche n'est pas geree
        if(!gere) {
                switch(k)
                {
                        //On veut voir le menu
                        case 'q':
                                fin();
                                break;
                        default:
                                break;
                }
        }
}

5. La classe Moteur▲

La classe Moteur contient quelques nouvelles fonctions :

Les nouvelles fonctions de la classe Moteur
Sélectionnez

    //Gestion du clavier
    void clavier(unsigned char k);

    //Est-ce que la partie est finie ?
    bool estFini();
    //Terminer la partie
    void fin();

    //Echange entre menu et jeu
    void echangeFonctions();

    //Initialise la partie
    void initJeu();

    //Est-ce que le jeu est en cours
    bool jeuEnCours();

La plupart des fonctions sont assez claires. Le moteur est maintenant capable de dire si le programme sous-jacent se termine. On peut échanger d'état entre la partie et le menu.

La fonction initJeu permet de demander au jeu de recommencer la partie. Ceci permet au menu de faire savoir qu'on veut une nouvelle partie. Enfin, la fonction jeuEnCours nous fait savoir si une partie est en cours ou si elle n'a pas commencé ou si elle est terminée.

Nous avons aussi introduit la fonction fin qui permettra au moteur de faire le nécessaire côté sauvegarde, affichage (un bouton Etes-vous sûr? par exemple) et nettoyage.

Ceci modifie bien sûr la fonction main. En effet, à la place d'avoir une variable booléenne done, nous utilisons maintenant la fonction fin pour signaler la fin du programme dans la boucle événementielle et nous utilisons la fonction estFini pour savoir si nous devons continuer le programme.

6. Conclusion▲

Ceci conclut cette troisième partie sur un simple jeu comme un pong. Comme vous le remarquez, ajouter un pseudo-moteur physique n'est pas vraiment compliqué si nous prenons le soin de mettre en place certaines hypothèses.

En effet, en comparant les positions de chaque objet, il est possible d'en déduire les collisions. Ensuite, nous remettons l'objet en place et nous modifions la vitesse de chaque objet. Cette technique, ne suivant pas entièrement les lois physiques suffit pour ce genre de petit programme et est une bonne introduction au sujet.

Jc

Liens
  • Sommaire du tutoriel;
  • Introduction;
  • Les bases du moteur;
  • Les collisions et un menu;
  • Améliorer les collisions; Image non disponible
  • Le score, la souris et les joueurs;
  • Le réseau;
  • Conclusion.

7. Téléchargements▲

Voici le code source pour ce tutoriel: (781 Ko) zip.

Voici la version pdf de cet article: (248 Ko).

Si vous avez des suggestions, remarques, critiques, si vous avez remarqué une erreur, ou bien si vous souhaitez des informations complémentaires, n'hésitez pas à me contacter !

8. Remerciements▲

J'aimerais remercier loka pour sa double relecture de cet article !

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2006 Jean Christophe Beyler. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -
  • Nous contacter
  • Participez
  • Hébergement
  • Informations légales

Copyright © 2000-2016 - www.developpez.com


Programmation de Jeux 2D: Un pong en SDL/OpenGL, Quatrième partie

Table des matières Plier Déplier

  • 1. Introduction
  • 2. Les collisions : version améliorée
    • 2.1. Motivations
    • 2.2. Présentation
    • 2.3. Algorithme
      • 2.3.a. Structure de données
      • 2.3.b. Calcul de zone
        • 2.3.b.1. La fonction enleverObjetDesZones
        • 2.3.b.2. La fonction recupereCoinObjet
        • 2.3.b.3. Les fonctions recupereZoneColonne et recupereZoneLigne
        • 2.3.b.4. Les fonctions enleverObjetZone
        • 2.3.b.5. Les fonctions pour ajouter un objet dans une zone
      • 2.3.c. Le calcul de collision
      • 2.3.d. La fonction updateObjets
      • 2.3.e. La fonction init
    • 2.4. Performances
    • 2.5. Allocation dynamique
  • 3. La vitesse des balles
  • 4. Les barres
    • 4.1. Chargement de la texture
    • 4.2. Affichage de l'objet
    • 4.3. Gestion de la collision
  • 5. Conclusion
  • 6. Téléchargements
  • 7. Remerciements

Nous n'avons jamais parlé de performances depuis le début de ce tutoriel donc je pense qu'il est temps que nous nous y attardions un peu. En faisant des tests de base, on voit qu'on perd beaucoup de temps dans la gestion des collisions. Cette partie va donc discuter d'une solution pour limiter cette perte et rendre notre code de gestion des collisions plus intelligent.

Bienvenue à cette quatrième partie sur la programmation du pong. Dans cette partie, nous allons revenir sur la gestion des collisions dans le but de limiter son impact au niveau des performances.

Cette partie présentera donc une technique inspirée des QuadTrees pour ne tester qu'une partie des objets (ceux qui se trouvent dans les environs). Et, à la fin de ce tutoriel, nous allons introduire un nouveau type d'objet : la barre.

Il est vrai que dans un jeu de pong, nous n'avons pas forcément besoin de chercher très loin ou d'utiliser un code ultra-performant. Néanmoins, il est souvent intéressant de trouver une meilleure technique qui pourra s'utiliser dans un projet plus complexe.

Question complexité, le code présenté dans la troisième partie n'était pas génial. En effet, si nous supposons que nous avons 50 objets dans le jeu, il fallait faire 2500 comparaisons. Cela ne semblera pas forcément énorme et pour étudier l'impact, j'ai fait quelques tests.

En utilisant le code de la troisième partie et en modifiant la taille de la fenêtre (la passant à 1024*768) et la taille des balles (passant de 30 à 20), voici un tableau récapitulatif du nombre d'images par seconde :

Nombre d'objets | Avec | Sans
50 | 490 | 490+
250 | 104 | 384
500 | 30 | 304

La colonne Avec est le nombre d'images par seconde en utilisant la gestion des collisions et la colonne Sans est le nombre sans cette gestion.

Comme vous le voyez, la gestion de collision demande tout de même beaucoup de ressources et ralentit tout de même le programme. Bien que cela reste raisonnable pour le cas des 50 objets, je pense que cela est plutôt dû au fait que la carte graphique et l'écran n'arrivent pas à afficher assez rapidement les images (d'où le + dans le tableau). Il faudra donc garder en tête ce problème de saturation dans la suite de l'article. Il faut aussi garder en tête que l'utilisation en force brute de la gestion de collision avec 500 objets nécessite le calcul de 500*500 calculs de collision (ce qui est égal à 250000 calculs...) par mise à jour de la position des objets.

Bien sûr, il faut relativiser l'importance de l'optimisation. Le problème, c'est que 250 objets qui sont en mouvement n'est pas forcément quelque chose d'anormal dans un grand jeu RTS et gérer les collisions ou les interactions entre les objets sont des choses primordiales. Si avec cette gestion, le nombre d'images par seconde est à 100, alors avec un affichage plus complexe qu'un pong, le jeu tournera à 1 ou 2 images par seconde...

2.2. Présentation▲

L'idée qui va nous permettre de gagner en nombre d'images par seconde et donc limiter les calculs de collisions est de classer les objets avec, comme facteur de tri, leur position dans le monde de jeu (dans notre cas, c'est donc la position dans la fenêtre).

Sachant bien sûr que nous allons donc tester les collisions d'un objet seulement avec les objets de sa zone. Voici une image montrant la fenêtre avec 10 balles :

Image non disponible
Un quadrillage possible

Le quadrillage montre que, par exemple, dans la zone en haut à gauche, seules deux balles sont potentiellement en collision. Lorsqu'on gérera une de ces balles, ce sera inutile de tester la collision avec les autres.

Par contre, que faire de la balle turquoise qui est sur la ligne ? Et bien, elle se retrouvera dans les deux zones en question. Dernière remarque, rien n'oblige d'avoir le même nombre de lignes que de colonnes dans le quadrillage.

2.3. Algorithme▲

Pour arriver à faire cette définition de zones, nous avons donc besoin de pouvoir calculer la zone dans laquelle se trouve un objet et connaître les autres objets de cette zone. Puisque nous déplacerons un seul objet à la fois, l'algorithme général de la mise à jour des objets deviendra donc :

Algorithme général
Sélectionnez

Enlever sa présence des zones auxquelles il appartient
Mis à jour de l'objet
Calcul de ses nouvelles zones
Calcul de collision avec les objets de ces zones

Bien entendu, la zone ne changera pas forcément à chaque mise à jour mais il faut bien savoir si nous avons changé de zone, donc il faut le vérifier à chaque mouvement.

2.3.a. Structure de données▲

Pour définir les structures de données nécessaires pour cet algorithme, nous allons introduire de nouvelles constantes dans le programme :

Constantes pour le quadrillage
Sélectionnez

const int QUADCOL = 5;
const int QUADROW = 5;

Nous avons donc défini un quadrillage de 5 colonnes et 5 lignes. Ensuite, pour gérer les objets de notre classe physique, nous devons ajouter un tableau à deux dimensions pouvant contenir ces objets.

Il faut remarquer que la taille minimale des zones est la moitié de la taille d'une balle + 1.

Puisqu'il est possible que tous les objets soient dans la même zone, nous devons avoir un tableau qui peut stocker tous les objets dans la même zone. Nous commencerons par montrer une allocation statique (plus simple d'un point de vue conception) définissant une structure qui contient le nombre d'objets dans la zone et un tableau contenant les numéros des objets.

Structure de zone
Sélectionnez

typedef struct sZone
{
int nobjets;
int objets[MAX_BALLES];
}SZone;

Ensuite nous pouvons déclarer, dans notre classe physique, notre tableau de zones. Remarquons que ceci déclare la taille maximale possible et ceci peut être amoindri si on utilise une allocation dynamique. Par contre, dans le cas d'un quadrillage de 5*5 avec 250 objets au maximum et 4 octets par entier, cette table utilisera approximativement 25k ce qui est raisonable.

Tableau de zones
Sélectionnez

//Tableau de zones
SZone tableau_zones[QUADLIGNE][QUADCOL];

2.3.b. Calcul de zone▲

Dans la suite logique des choses, nous devons maintenant pouvoir calculer la zone de chaque objet. En fait, pour ce calcul, il suffira de calculer la zone des quatre coins de l'objet.

Nous aurons donc deux fonctions, une qui demande d'enlever un objet des zones et une fonction qui ajoute un objet aux zones. Voici le code qui enlève un objet donné du tableau des zones.

2.3.b.1. La fonction enleverObjetDesZones▲

Enlever un objet des zones
Sélectionnez

void Physique::enleverObjetDesZones(Objet *objs, int idx)
{
      double x=0,y=0;
      int oldzonei=-1, oldzonej=-1;
      int newzonei,newzonej;
      //Pour chaque coin du rectangle
      for(int i=0;i<4;i++)
      {
            //Recupere le coin
            recupereCoinObjet(objs[idx],i,x,y);

            //Calcul de la zone
            newzonei = recupereZoneLigne(y);
            newzonej = recupereZoneColonne(x);

            //Si c'est une autre zone
            if((newzonei!=oldzonei)||(newzonej!=oldzonej))
                  {
                        oldzonei = newzonei;
                        oldzonej = newzonej;
                        enleverObjetZone(idx,newzonei,newzonej);
                  }
      }
}

Il y a bien sûr beaucoup de nouvelles fonctions dans ce code. Nous allons les voir dans l'ordre après avoir expliqué l'idée générale de la fonction.

Tout d'abord, nous allons étudier chaque coin de l'objet. Pour connaître la position de chaque coin, nous allons appeler la fonction recupereCoinObjet. Pour une question d'optimisation, cette fonction se trouve dans la classe Physique. On pourrait la mettre dans la classe Objet mais on perdrait la possibilité de mettre le code en inline (la plupart des compilateurs ne font pas d'inline entre fichiers...).

Une fois que nous avons la position du coin, on utilisera les fonctions recupereZoneLigne et recupereZoneColonne qui permettent respectivement d'avoir la ligne et la colonne correspondante pour le tableau des zones.

Le test qui suit nous permet de ne pas demander le retrait d'un objet si, d'un coin à un autre, nous sommes restés dans la même zone. Il est possible que nous demanderons tout de même deux fois le retrait d'une même zone mais cela est mieux que 4 fois!

Bref, présenté comme cela, cette fonction est assez directe mais en fait elle utilise 4 autres fonctions pour faire son travail. Présentons rapidement la fonction qui nous donne la position du coin.

2.3.b.2. La fonction recupereCoinObjet▲

La fonction recupereCoinObjet
Sélectionnez

inline void Physique::recupereCoinObjet(Objet &o, int i, double &x, double &y)
{
      //On regarde chaque coin
      switch(i)
      {
            case 0:
                  //En haut a gauche
                  x = o.getX();
                  y = o.getY();
                  break;
            case 1:
                  //En haut a droite
                  x = o.getX()+o.getW();
                  y = o.getY();
                  break;
            case 2:
                  //En bas a droite
                  x = o.getX()+o.getW();
                  y = o.getY()+o.getH();
                  break;
            case 3:
                  //En bas a gauche
                  x = o.getX();
                  y = o.getY()+o.getH();
                  break;
            default:
                  break;
      }

}

Comme vous le voyez, le paramètre i permet de définir quel coin nous intéresse. Ensuite, en utilisant un switch, la fonction est capable de mettre à jour les paramètres x et y.

2.3.b.3. Les fonctions recupereZoneColonne et recupereZoneLigne▲

Les fonctions recupereZoneColonne et recupereZoneLigne
Sélectionnez

inline int Physique::recupereZoneColonne(double x)
{
      const int PIXPARCOL = WIDTH/QUADCOL;
      int res = (int) (x/PIXPARCOL);
      if(res>=QUADCOL)
            {
            return QUADCOL-1;
            }
      return res;
}

inline int Physique::recupereZoneLigne(double y)
{
      const int PIXPARLIGNE = WIDTH/QUADLIGNE;
      int res = (int) y/PIXPARLIGNE;
      if(res>=QUADLIGNE)
            {
            return QUADLIGNE-1;
            }
      return res;
}

Ces deux fonctions sont relativement simples si vous vous souvenez de l'image du quadrillage. Le but est de savoir quel indice représente la zone d'une position en pixel. Si nous avons une largeur de 800 et 5 colonnes, chaque colonne fait donc 160 pixels. C'est par cela que commence ces fonctions : le calcul de la largeur (ou hauteur) d'une zone.

Ensuite, il suffit de calculer à quelle zone appartient un objet par une simple division et le test final permet de récupérer les objets qui sont aux extrêmes de l'écran (en effet, un effet de bord du calcul par entier, on perd un peu de précision et il y a donc un risque de débordement).

2.3.b.4. Les fonctions enleverObjetZone▲

Nous arrivons enfin à la fonction qui va enlever d'une zone l'indice d'un objet :

La fonction enleverObjetZone
Sélectionnez

void Physique::enleverObjetZone(int idx, int i, int j)
{
   int k;
   int *ptr = tableau_zones[i][j].objets,
       size = tableau_zones[i][j].nobjets;

   for(k=0;k<size;k++)
   {
      if(ptr[k]==idx)
         {
         //On efface en echangeant avec le dernier
         ptr[k] = ptr[size-1];
         tableau_zones[i][j].nobjets--;
         break;
         }
   }
}

Ce code est assez simple : on parcourt le tableau représentant la zone en cherchant l'indice de l'objet à enlever. Si on le trouve, on écrase la case avec le dernier objet du tableau et on décrémente le nombre d'objets dans la zone.

Nous avons enfin présenté chaque fonction qui sert à enlever un objet des zones auxquelles il appartient. Comme vous le voyez, il n'y a rien de particulier et rien de très difficile. Il suffit de faire un peu attention aux indices...

2.3.b.5. Les fonctions pour ajouter un objet dans une zone▲

Pour ajouter un objet, nous faisons les mêmes opérations sauf qu'à la place d'appeler enleverObjetZone, nous allons appeler une fonction ajouterObjetZone. Pour garder la longueur de ce tutoriel convenable, je ne vais pas mettre le code (surtout qu'il n'y a pas grand chose de compliqué dans le code !).

2.3.c. Le calcul de collision▲

Il faut maintenant changer la fonction collisionObjet pour qu'elle prenne en compte l'utilisation des zones. Pour garder les deux fonctions, nous nommerons la nouvelle fonction collisionObjetZone et voici son prototype :

Le prototype de la fonction collisionObjetZone
Sélectionnez

//Fonction qui gere la collision avec l'objet i mais en utilisant les zones
static int collisionObjetZone(std::vector<Objet> &objets, int idx);

Cette fonction prend le tableau des objets et l'indice de l'objet à considérer. Puisque nous n'avons plus besoin de parcourir le tableau, nous ne passons plus sa taille. Pour ne pas faire trop long, nous présenterons seulement la partie la plus intéressante de cette fonction (en gros, nous omettons la déclaration des variables et le dernier return) :

La fonction collisionObjetZone
Sélectionnez

      int oldzonei=-1, oldzonej=-1;

      x1 = objets[idx].getX();
      y1 = objets[idx].getY();
      w1 = objets[idx].getW();
      h1 = objets[idx].getH();

      //Si c'est un cercle, on recupere son centre
      if(objets[idx].getType()==CERCLE)
            {
            cx1 = x1+w1/2;
            cy1 = y1+h1/2;
            cr1 = w1/2;
            cercle = true;
            }

      //Pour chaque coin
      for(i=0;i<4;i++)
      {
            //Recupere le coin
            recupereCoinObjet(objets[idx],i,x,y);

            //Calcul de la zone
            newzonei = recupereZoneLigne(y);
            newzonej = recupereZoneColonne(x);

            //Si c'est une autre zone
            if((newzonei!=oldzonei)||(newzonej!=oldzonej))
                  {
                        oldzonei = newzonei;
                        oldzonej = newzonej;

                        //Ok on va regarder dans cette zone
                        nobjets_zone = tableau_zones[newzonei][newzonej].nobjets;
                        objets_zone = tableau_zones[newzonei][newzonej].objets;

                        //On parcourt tous les objets de cette zone
                        for(k=0;k<nobjets_zone;k++)
                        {
                              l = objets_zone[k];

                              x2 = objets[l].getX();
                              y2 = objets[l].getY();
                              w2 = objets[l].getW();
                              h2 = objets[l].getH();

                              //Si on a deux cercles
                              if( cercle && (objets[l].getType()==CERCLE))
                              {
                                    cx2 = x2 + w2/2;
                                    cy2 = y2 + h2/2;
                                    cr2 = w2/2;

                                    if(collisionCercle(cx1,cy1,cr1,cx2,cy2,cr2))
                                          return l;
                              }
                              else //Sinon on fait une collision entre rectangles
                              {
                                    if(collisionRect(x1,y1,w1,h1,x2,y2,w2,h2))
                                          return l;
                              }
                        }
                  }
      }
return -1;

Si on comparait avec la version qui n'utilisait pas de zone, on remarquerait que la boucle k interne est du même genre que l'originale. Nous avons simplement entouré cette boucle d'un calcul de zone pour chaque coin de l'objet.

Une petite différence est la disparition d'un test pour vérifier qu'on ne calcule pas la collision de l'objet sur lui-même. En effet, l'algorithme demande d'enlever l'objet du tableau de zone avant la gestion des collisions. Puisque cet indice n'est plus dans le tableau, le test est inutile.

Plus important est la présence d'un test sur le type de l'objet. En effet, à la fin de ce tutoriel, nous allons avoir un deuxième type d'objet : les barres ! Du coup, nous avons défini une énumération contenant tous les types. Ceci nous permettra de savoir quel genre de calcul de collision est nécessaire, mais aussi le genre de réaction qu'auront les objets lors de collisions.

L'énumération des types
Sélectionnez

enum Type {CERCLE, BARRE};

Ceci rajoute bien sûr un champs membre dans la classe Objet et une fonction qui permet de récupérer le type de l'objet.

2.3.d. La fonction updateObjets▲

Enfin la dernière chose à modifier est la fonction d'entrée updateObjets qui s'occupera de la gestion du tableau de zones et d'appeler la nouvelle fonction collisionObjetZone. Pour respecter l'algorithme, il suffira d'enlever l'objet du tableau avant la modification de sa position :

Enlever l'indice de l'objet
Sélectionnez

//Sauvegarde de l'ancienne position
oldx = objets[i].getX();
oldy = objets[i].getY();

//Enlever des zones
enleverObjetDesZones(objets, i);

//Mise a jour de la position
objets[i].updatePos();

Et bien sûr, avant de passer au prochain objet, il ne faut pas oublier de rajouter l'indice de l'objet dans le tableau :

Ajouter l'indice de l'objet
Sélectionnez

//Ajouter dans les zones
ajouterObjetDansZones(objets, i);

2.3.e. La fonction init▲

Notre classe Physique contient maintenant des informations sur l'état du jeu et la position des balles. Il faut donc pouvoir initialiser le tableau pour le mettre à zéro. Ceci se fait lors du début du programme ou lorsque l'utilisateur veut recommencer une partie.

La fonction init sert donc à mettre chaque case du maillage à zéro :

La fonction init
Sélectionnez

//Fonction d'initialisation
bool Physique::init()
{
int i,j;
for(i=0;i<QUADLIGNE;i++)
  for(j=0;j<QUADCOL;j++)
    {
    tableau_zones[i][j].nobjets = 0;
    }
return true;
}

Enfin, l'instance de jeu appellera l'initialisation du moteur physique dans la fonction recommence :

La fonction recommence de la classe Jeu
Sélectionnez

//Recommence la partie
void Jeu::recommence()
{
    objets.clear();
    Physique::init();
    jeuEnCours = true;
}

2.4. Performances▲

Voici le tableau que vous avez vu au début du tutoriel avec une nouvelle colonne. Zone donne le nombre d'images par seconde de cette nouvelle version de calcul de collisions.

Nombre d'objets | Avec | Zone | Sans
50 | 490 | 490+ | 490+
250 | 104 | 380 | 384
500 | 30 | 240 | 304

Comme vous le voyez, notre nouvelle version permet d'avoir le même comportement que la version sans gestion des collisions jusqu'à 250 objets. Ensuite, pour 500 objets, nous avons tout de même un gain considérable par rapport à la version de base.

Enfin, nous pouvons faire remarquer que, si l'utilisation mémoire n'est pas trop un facteur, passer à un quadrillage 15*15 permet d'avoir le même nombre d'images par secondes avec gestion de collisions qu'une version sans pour un programme qui fait interagir 500 objets ! Ceci en gardant en tête qu'il y a sûrement un facteur de saturation au niveau de la fréquence d'affichage, mais cela est nettement mieux qu'avoir 30 images par secondes...

A la fin de ce tutoriel, il y aura une version du pong qui contiendra cette allocation statique. L'allocation statique a l'avantage d'être plus facile à concevoir, par contre, le gaspillage de mémoire peut souvent être évité en utilisant l'allocation dynamique. C'est ce que nous allons montrer par la suite.

2.5. Allocation dynamique▲

Nous avons vu que les performances de ce système dépendent largement de la position des objets mais aussi de la taille du quadrillage. Si le quadrillage est plus fin, la gestion des collisions peut se faire plus rapidement. Par contre, notre utilisation mémoire est tout de même un peu gourmande, donc nous allons présenter ici comment résoudre ce problème en passant par une allocation dynamique.

Bien que ceci ne soit pas nécessaire, cela est tout de même intéressant de pouvoir garder le gain de temps et de limiter l'occupation mémoire. Bien qu'on n'en ait pas parlé, un quadrillage 15*15 avec 500 balles utilise tout de même 450 kO !

Pour limiter ce nombre, nous allons donc directement utiliser un tableau de type std::vector. Nous n'aurons plus besoin d'une structure puisque le type std::vector contient la méthode size qui nous le fournira.

Nouveau tableau de zones
Sélectionnez

//Tableau de zones
static std::vector<int> tableau_zones[QUADLIGNE][QUADCOL];

Ensuite, il faut mettre à jour le code pour gérer ce changement de type. Nous commençons par changer la déclaration de la variable tableau_zones :

Déclaration du tableau de zones
Sélectionnez

std::vector<int> Physique::tableau_zones[QUADLIGNE][QUADCOL];

Ensuite, à la place d'utiliser la variable nobjets, nous passons par la méthode size dans les fonctions enleverObjetZone, ajouterObjetZone et collisionObjetZone.

Voici le code pour enlever un objet d'une zone :

Enlever un objet d'une zone
Sélectionnez

void Physique::enleverObjetZone(int idx, int i, int j)
{
    int k;
    std::vector<int> &ptr = tableau_zones[i][j];
    int size = tableau_zones[i][j].size();

    for(k=0;k<size;k++)
    {
        if(ptr[k]==idx)
        {
            //On efface en echangeant avec le dernier
            ptr[k] = ptr[size-1];
            ptr.pop_back();
            break;
        }
    }
}

Comme vous le voyez, ce code copie le dernier objet du tableau et ensuite utilise la méthode pop_back pour diminuer la taille du vecteur.

Pour ajouter un objet dans une zone, on utilise cette nouvelle version de la fonction ajouterObjetZone :

Ajouter un objet d'une zone
Sélectionnez

void Physique::ajouterObjetZone(int idx, int i, int j)
{
    unsigned int k,
        size = tableau_zones[i][j].size();
    std::vector<int> &ptr = tableau_zones[i][j];

    for(k=0;k<size;k++)
    {
        if(ptr[k]==idx)
        {
            break;
        }
    }

    if((k==size)&&(size<MAX_BALLES))
        {
        ptr.push_back(idx);
        }
}

Cette nouvelle version utilise la fonction push_back pour ajouter un élément dans un vecteur. La dernière fonction qui a été modifiée est collisionObjetZone, voici la portion de la fonction qui a été modifiée :

Changement dans collisionObjetZone
Sélectionnez

    //Ok on va regarder dans cette zone
    nobjets_zone = tableau_zones[newzonei][newzonej].size();
    std::vector<int> &objets_zone = tableau_zones[newzonei][newzonej];

    //On parcourt tous les objets de la zone
    for(k=0;k<nobjets_zone;k++)
       {
       l = objets_zone[k];

Ce qui est intéressant est la facilité offerte par le type std::vector pour la gestion dynamique de la taille des vecteurs. Ce prochain tableau montre les performances avec 3000 objets de taille 5 (la mémoire est la mémoire utilisée par le programme en méga-octets) :

Version | Mémoire | Taux de rafraîchissement
Gestion naïve | 16 | 2
Statique | 60 | 65
Dynamique | 16 | 68
Sans | 16 | 150

Enfin, voici une image qui montre 3000 balles :

Image non disponible
3000 balles

Une dernière remarque sur cet algorithme pour dire qu'il ne faut pas non plus prendre n'importe quelle taille pour le quadrillage. En effet, il faut que la largeur (ou hauteur) soit plus grand que le rayon de la plus grande balle. En effet, on regarde les zones des coins des balles. Il faut donc qu'il n'y ait pas de trou entre deux coins. Sinon, une collision peut se produire sans que nous la détections. Une bonne solution est de définir les constantes comme ceci :

Définition du quadrillage
Sélectionnez

const int QUADCOL=WIDTH/TAILLE_BALLE;
const int QUADLIGNE=HEIGHT/TAILLE_BALLE;

A la fin de ce tutoriel, vous aurez le programme permettant d'avoir les 3000 balles gérées par une allocation statique (puisqu'à la fin de ce tutoriel, vous aurez le source utilisant entièrement l'allocation dynamique mais aussi les barres.

3. La vitesse des balles▲

Pour le moment, la mise à jour de la position de la balle se fait comme ceci :

Code pour la mise à jour des objets
Sélectionnez

//Calcul de la nouvelle position
double x = pos.getX() + vitdir.getX(),
      y = pos.getY() + vitdir.getY(),
      w = taille.getX(),
      h = taille.getY();

Comme vous le voyez, il n'y pas de gestion du temps. Ceci n'est pas une très bonne idée puisque, si ce programme est utilisé sur différents ordinateurs, la vitesse de la balle ne sera pas la même. En utilisant le temps, on harmonise les déplacements.

Cette solution n'a pas que des inconvénients : s'il y a une discontinuité dans le taux de rafraîchissement (le système d'exploitation fait autre chose par exemple), le programme continuera comme si de rien n'était. En utilisant le temps, après une discontinuité, il y aura un saut dans le déplacement. Lors d'un calcul de collision, cela n'est pas très pratique. Une solution hybride consiste à calculer la vitesse moyenne de temps en temps, et utiliser cette vitesse pour le déplacement.

En voulant garder le code relativement simple, nous allons tout de même utiliser la version utilisant le temps. Voici le code que nous allons utiliser :

Nouveau code pour la mise à jour des objets
Sélectionnez

//Heure actuelle
long tps = SDL_GetTicks() - derniertps;
//Calcul de la nouvelle position
double x = pos.getX() + vitdir.getX() * tps * RATIOVITESSE,
      y = pos.getY() + vitdir.getY() * tps * RATIOVITESSE,
      w = taille.getX(),
      h = taille.getY();

//Mise à jour derniertps
setDernierTps();

En ajoutant une variable derniertps qui représente la dernière mise à jour. En utilisant la différence entre le temps actuel et la dernière mise à jour, on est capable de calculer la distance de déplacement. Vous remarquerez la présence de la fonction setDernierTps qui permet de mettre à jour la variable dernierTps.

La mise en place de dernierTps
Sélectionnez

class Objet
{
  private :
     ...
     //Dernier temps de mise à jour
     long dernierTps;
  pubic :
     ...
     //Mettre a jour la variable derniertps
     void setDernierTps();

Le fait que la fonction setDernierTps soit déclaré comme une fonction publique n'est pas un hasard. En effet, nous allons avoir besoin de cette fonction lorsque l'utilisateur met le jeu en pause. Avant, nous mettions à jour la position à chaque mise à jour, indépendamment du temps entre les deux.

Cela veut dire que si l'utilisateur met le jeu en pause et revient 15 minutes plus tard. Lorsque le jeu reprend, les balles ne bougeront pas plus rapidement. Par contre, maintenant, vous imaginez bien que la variable tps va être trop grande, cela provoquera donc des sauts.

Une solution facile est de toucher les objets pour mettre à jour leur variable dernierTps. Nous déclarons donc une nouvelle fonction toucheObjets dans la classe Jeu pour les mettre à jour.

La fonction toucheObjets
Sélectionnez

void Jeu::toucheObjets()
{
    unsigned int i,
            nobjets = objets.size();

    for(i=0;i<nobjets;i++)
        {
        objets[i].setDernierTps();
        }
}

Cette fonction nous permettra donc de mettre toutes les variables dernierTps à jour et donc d'éviter les sauts. Bien sûr, il faut encore appeler cette fonction au bon moment. Ceci se fera lorsque le jeu reprend. Nous pouvons le gérer en complétant donc les fonctions de la classe Moteur :

La fonction echangeFonctions
Sélectionnez

//Echange entre menu et jeu
void Moteur::echangeFonctions()
{
    dans_menu = !dans_menu;
    if(!dans_menu) {
        jeu->toucheObjets();
    }
}

Dernière remarque, ceci modifie bien sûr la norme qui est utilisée pour les vecteurs. En effet, SDL_GetTicks retourne le temps en terme de millisecondes. Ceci veut donc dire qu'une norme correcte serait de l'ordre de 0.1 pour que l'objet se déplace d'une centaine de pixels en une seconde. C'est donc à cela que sert la constante RATIOVITESSE.

4. Les barres▲

La dernière chose (je le promets) que nous allons faire dans ce tutoriel est d'ajouter un nouveau type d'objet. Comme vous l'avez sûrement remarqué nous avions déjà préparé un peu le terrain pour cet objet. En effet, nous avons déclaré cet objet dans l'enum Type :

L'enum Type
Sélectionnez
enum Type {CERCLE, BARRE};

Ensuite, si vous vous souvenez de la fonction collisionObjetZone, nous avons commencé à intégrer la présence de types d'objets différents. En effet, en fonction du type des objets, nous utilisons la collision la plus adaptée.

Il y a donc un peu de travail nécessaire pour ajouter ce nouveau type. Il faudra gérer son affichage et la réaction qu'il peut y avoir face à une collision.

En ce qui concerne l'affichage, tout sera géré par la classe Objet. Il faudra bien sûr charger une nouvelle texture pour nos barres, nous allons ajouter une deuxième variable statique dans la classe Objet pour contenir son indice et lors de l'affichage de l'objet, son type décidera entre les deux indices pour l'appel glBindTexture.

4.1. Chargement de la texture▲

Nous avons donc besoin d'une deuxième variable statique pour la texture :

Le chargement de deux textures
Sélectionnez

static GLuint txtpion, txtbarre;

Pour ce qui est du chargement de la texture, cela se passe dans la fonction membre initTextures de la classe Jeu. Pour charger les deux textures, nous allons encapsuler le code existant dans une petite boucle qui, grâce à un if, chargera d'abord une texture puis la deuxième.

Le chargement de deux textures
Sélectionnez

    for(k=0;k<2;k++)
    {

        if(k==0)
        {
            //On charge l'image "data/balle.bmp"
            tmpsurf = SDL_LoadBMP("data/balle.bmp");
        }
        else
        {
            //On charge l'image "data/barre.bmp"
            tmpsurf = SDL_LoadBMP("data/barre.bmp");
        }

         //Gestion de l'erreur
         if(tmpsurf==NULL)
         {
               std::cout << "Erreur dans le chargement de la surface : "
                         << SDL_GetError() << std::endl;
               return false;
         }

Ensuite, nous avons le même code que pour la deuxième partie. Par contre, à la fin de la boucle, il faut passer à la classe Objet de l'indice de la texture, un autre if permet de gérer ce problème :

Interaction avec la classe Objet
Sélectionnez

        if(k==0)
        {
            //On passe la texture aux objets de type balle
            Objet::setTxtBalle(txtsurf);
        }
        else
        {
            //On passe la texture aux objets de type barre
            Objet::setTxtBarre(txtsurf);
        }

A ce point du code, les deux textures sont donc chargées et la classe Objet a maintenant les deux variables statiques txtpions et txtbarre sont à jour.

4.2. Affichage de l'objet▲

Lorsque nous allons vouloir afficher l'objet, il faudra simplement regarder le type de l'objet avant de faire l'appel au glBindTexture.

Interaction avec la classe Objet
Sélectionnez

if(type==CERCLE)
   {
   glBindTexture(GL_TEXTURE_2D, txtpion);
   }
else
   {
   glBindTexture(GL_TEXTURE_2D, txtbarre);
   }

Bien sûr, si nous avions plus de deux types, un tableau d'indice de textures OpenGL serait plus approprié.

4.3. Gestion de la collision▲

Enfin, pour la gestion de la collision, il suffit de dire que les barres ne sont pas affectées par les collisions et qu'une balle qui est en collision avec la barre repart dans la direction du centre de la barre vers la balle. En calculant la position du centre de la barre, nous avons facilement ce vecteur :

Sachant que pour la collision Cercle-Cercle nous avons déjà calculé cette direction, la seule chose qu'il reste à faire est de ne plus faire la somme des vitesses comme avant :

Collision avec une barre
Sélectionnez

//Si l'objet res n'est pas un cercle, on prend la direction (newx, newy)
//et met la vitesse à 0.1
if(objets[res].getType()!=CERCLE)
   {
   objets[i].setDirVitesse(newvx,newvy);

   }
else //Sinon on ajoute la direction (newx, newy)
   {
     objets[i].setDirVitesse(objets[i].getVX()+newvx,
          objets[i].getVY()+newvy);
   }
objets[i].setVitesse(0.1);

Donc, lorsqu'une balle rentre en collision avec une barre, son ancienne direction est remplacée par une direction qui dépend de la position de l'impact (un peu comme dans un casse brique).

Pour montrer l'utilisation des barres, nous allons ajouter pour ce tutoriel le code nécessaire à la création de 4 barres dans la fonction recommence :

La fonction recommence
Sélectionnez

//Recommence la partie
void Jeu::recommence()
{
    Objet o;

    objets.clear();
    Physique::init();
    jeuEnCours = true;

    //Premiere barre
    o.setCouleur(1.0,0.0,0.0);
    o.setPos(WIDTH/2-LARG_BARRE/2,5*HEIGHT/6 - HAUT_BARRE/2);
    o.setTaille(LARG_BARRE,HAUT_BARRE);
    addObjet(o);

    //2eme barre
    o.setCouleur(1.0,1.0,1.0);
    o.setPos(WIDTH/2-LARG_BARRE/2,HEIGHT/6-HAUT_BARRE/2);
    o.setTaille(LARG_BARRE,HAUT_BARRE);
    addObjet(o);

    //3eme barre
    o.setCouleur(0.0,1.0,0.0);
    o.setTaille(HAUT_BARRE,LARG_BARRE);
    o.setPos(WIDTH/6-HAUT_BARRE/2,HEIGHT/2 -LARG_BARRE/2);
    addObjet(o);

    //4eme barre
    o.setCouleur(0.0,1.0,1.0);
    o.setTaille(HAUT_BARRE,LARG_BARRE);
    o.setPos(5*WIDTH/6-HAUT_BARRE/2,HEIGHT/2 -LARG_BARRE/2);
    addObjet(o);
}

Comme vous le voyez, ce n'est pas difficile d'ajouter des barres dans le jeu. Nous avons simplement défini en plus la taille souhaitée des barres :

Taille des barres
Sélectionnez

const int LARG_BARRE=120;
const int HAUT_BARRE=25;

Bien sûr, qui dit taille de barre, dit taille du quadrillage pour les zones. La taille d'une barre étant plus grande qu'une balle, il faudrait mieux changer le calcul de QUADCOL et QUADLIGNE.

Voici une solution pour garder le code générique :

Calcul de QUADCOL et QUADLIGNE
Sélectionnez

const int QUADCOL=WIDTH/ (
            (TAILLE_BALLE>LARG_BARRE)?
                ((TAILLE_BALLE>HAUT_BARRE)?
                    TAILLE_BALLE:
                    HAUT_BARRE):
                ((HAUT_BARRE>LARG_BARRE)?
                    HAUT_BARRE:
                    LARG_BARRE)
           );
const int QUADLIGNE=HEIGHT/ (
            (TAILLE_BALLE>LARG_BARRE)?
                ((TAILLE_BALLE>HAUT_BARRE)?
                    TAILLE_BALLE:
                    HAUT_BARRE):
                ((HAUT_BARRE>LARG_BARRE)?
                    HAUT_BARRE:
                    LARG_BARRE)
           );

Certes, l'opérateur ternaire n'est pas l'opérateur favori de beaucoup de programmeurs mais il est pratique ici. En effet, nous avons trois tailles possibles pour ce jeu (et cela demeurera trois pour le reste de cette série) donc nous pouvons nous permettre de faire un calcul sur le maximum des trois tailles possibles : la taille d'une balle, la largeur et la hauteur d'une barre. Ce calcul nous permet de garder sous forme de constante la taille du quadrillage.

Une dernière image montre la présence de quatre barres dans la fenêtre avec vingt balles :

Image non disponible
L'introduction des barres

5. Conclusion▲

Nous avons vu trois grands concepts dans ce tutoriel. Premièrement, une solution pour réduire considérablement l'impact de la gestion des collisions a été mise en place. En utilisant une solution utilisant une allocation dynamique (utilisant le type std::vector), nous avons pu gérer correctement les collisions de 3000 balles...

Ensuite, nous avons amélioré le calcul des déplacements pour le rendre indépendant du taux de rafraîchissement. Cette solution a ajouté une petite fonction pour gérer la mise en pause de jeu.

Enfin, nous avons ajouté un nouveau type d'objet : la barre. Cette barre ne bouge pas comme les balles et n'est pas affectée par les collisions avec les balles. On a pu facilement gérer la nouvelle texture et le comportement des balles lorsqu'elles sont en contact avec la barre.

Jc

Liens
  • Sommaire du tutoriel;
  • Introduction;
  • Les bases du moteur;
  • Les collisions et un menu;
  • Améliorer les collisions; Image non disponible
  • Le score, la souris et les joueurs;
  • Le réseau;
  • Conclusion.

6. Téléchargements▲

Voici le code source pour ce tutoriel :

  • La version avec 3000 balles et allocation statique : (782 Ko) zip.
  • La version finale avec l'utilisation du temps dans le mouvement, l'allocation dynamique et l'introduction des barres : (790 Ko) zip.

Voici la version pdf de cet article: (237 Ko).

Si vous avez des suggestions, remarques, critiques, si vous avez remarqué une erreur, ou bien si vous souhaitez des informations complémentaires, n'hésitez pas à me contacter !

7. Remerciements▲

J'aimerais remercier loka et khayyam90 pour leur relecture de cet article.

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2006 Jean Christophe Beyler. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -
  • Nous contacter
  • Participez
  • Hébergement
  • Informations légales

Copyright © 2000-2016 - www.developpez.com


glAccum.3gl

Name

  glAccum - operate on the accumulation	buffer

C Specification

  void glAccum(	GLenum op,
        GLfloat	value )

Parameters

  op	 Specifies the accumulation buffer operation.  Symbolic	constants
     GL_ACCUM, GL_LOAD, GL_ADD, GL_MULT, and GL_RETURN are accepted.

  value	 Specifies a floating-point value used in the accumulation buffer
     operation.  op	determines how value is	used.

Description

  The accumulation buffer is an	extended-range color buffer.  Images are not
  rendered into	it.  Rather, images rendered into one of the color buffers
  are added to the contents of the accumulation	buffer after rendering.
  Effects such as antialiasing (of points, lines, and polygons), motion	blur,
  and depth of field can be created by accumulating images generated with
  different transformation matrices.

  Each pixel in	the accumulation buffer	consists of red, green,	blue, and
  alpha	values.	 The number of bits per	component in the accumulation buffer
  depends on the implementation. You can examine this number by	calling
  glGetIntegerv	four times, with arguments GL_ACCUM_RED_BITS,
  GL_ACCUM_GREEN_BITS, GL_ACCUM_BLUE_BITS, and GL_ACCUM_ALPHA_BITS,
  respectively.	 Regardless of the number of bits per component, however, the
  range	of values stored by each component is [-1, 1].	The accumulation
  buffer pixels	are mapped one-to-one with frame buffer	pixels.

  glAccum operates on the accumulation buffer.	The first argument, op,	is a
  symbolic constant that selects an accumulation buffer	operation.  The
  second argument, value, is a floating-point value to be used in that
  operation.  Five operations are specified: GL_ACCUM, GL_LOAD,	GL_ADD,
  GL_MULT, and GL_RETURN.

  All accumulation buffer operations are limited to the	area of	the current
  scissor box and are applied identically to the red, green, blue, and alpha
  components of	each pixel.  The contents of an	accumulation buffer pixel
  component are	undefined if the glAccum operation results in a	value outside
  the range [-1,1].  The operations are	as follows:

  GL_ACCUM  Obtains R, G, B, and A values from the buffer currently selected
        for	reading	(see glReadBuffer).  Each component value is divided
        by 2n-1, where n is	the number of bits allocated to	each color
        component in the currently selected	buffer.	 The result is a
        floating-point value in the	range [0,1], which is multiplied by
        value and added to the corresponding pixel component in the
        accumulation buffer, thereby updating the accumulation buffer.

  GL_LOAD   Similar to GL_ACCUM, except	that the current value in the
        accumulation buffer	is not used in the calculation of the new
        value.  That is, the R, G, B, and A	values from the	currently
        selected buffer are	divided	by 2n-1, multiplied by value, and
        then stored	in the corresponding accumulation buffer cell,
        overwriting	the current value.

  GL_ADD    Adds value to each R, G, B,	and A in the accumulation buffer.

  GL_MULT   Multiplies each R, G, B, and A in the accumulation buffer by
        value and returns the scaled component to its corresponding
        accumulation buffer	location.

  GL_RETURN Transfers accumulation buffer values to the	color buffer or
        buffers currently selected for writing.  Each R, G,	B, and A
        component is multiplied by value, then multiplied by 2n-1,
        clamped to the range [0,2n-1], and stored in the corresponding
        display buffer cell.  The only fragment operations that are
        applied to this transfer are pixel ownership, scissor, dithering,
        and	color writemasks.

  The accumulation buffer is cleared by	specifying R, G, B, and	A values to
  set it to with the glClearAccum directive, and issuing a glClear command
  with the accumulation	buffer enabled.

Notes

  Only those pixels within the current scissor box are updated by any glAccum
  operation.

Errors

  GL_INVALID_ENUM is generated if op is	not an accepted	value.

  GL_INVALID_OPERATION is generated if there is	no accumulation	buffer.

  GL_INVALID_OPERATION is generated if glAccum is executed between the
  execution of	glBegin	and the	corresponding execution	of glEnd.

Associated Gets

  glGet	with argument GL_ACCUM_RED_BITS
  glGet	with argument GL_ACCUM_GREEN_BITS
  glGet	with argument GL_ACCUM_BLUE_BITS
  glGet	with argument GL_ACCUM_ALPHA_BITS

See Also

  glBlendFunc, glClear,	glClearAccum, glCopyPixels, glGet, glLogicOp,
  glPixelStore,	glPixelTransfer, glReadPixels, glReadBuffer, glScissor,
  glStencilOp

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glAlphaFunc.3gl

Name

  glAlphaFunc -	specify	the alpha test function

C Specification

  void glAlphaFunc( GLenum func,
            GLclampf ref )

Parameters

  func	Specifies the alpha comparison function.  Symbolic constants
    GL_NEVER, GL_LESS, GL_EQUAL, GL_LEQUAL,	GL_GREATER, GL_NOTEQUAL,
    GL_GEQUAL, and GL_ALWAYS are accepted.	The default function is
    GL_ALWAYS.

  ref	Specifies the reference	value that incoming alpha values are compared
    to.  This value	is clamped to the range	0 through 1, where 0
    represents the lowest possible alpha value and 1 the highest possible
    value.	The default reference is 0.

Description

  The alpha test discards fragments depending on the outcome of	a comparison
  between the incoming fragment's alpha	value and a constant reference value.
   glAlphaFunc specifies the reference and comparison function.	 The
  comparison is	performed only if alpha	testing	is enabled.  (See glEnable
  and glDisable	of GL_ALPHA_TEST.)

  func and ref specify the conditions under which the pixel is drawn.  The
  incoming alpha value is compared to ref using	the function specified by
  func.	 If the	comparison passes, the incoming	fragment is drawn,
  conditional on subsequent stencil and	depth buffer tests. If the comparison
  fails, no change is made to the frame	buffer at that pixel location.

  The comparison functions are as follows:

  GL_NEVER	    Never passes.

  GL_LESS	    Passes if the incoming alpha value is less than the
            reference value.

  GL_EQUAL	    Passes if the incoming alpha value is equal	to the
            reference value.

  GL_LEQUAL	    Passes if the incoming alpha value is less than or equal
            to the reference value.

  GL_GREATER	    Passes if the incoming alpha value is greater than the
            reference value.

  GL_NOTEQUAL	    Passes if the incoming alpha value is not equal to the
            reference value.

  GL_GEQUAL	    Passes if the incoming alpha value is greater than or
            equal to the reference value.

  GL_ALWAYS	    Always passes.

  glAlphaFunc operates on all pixel writes, including those resulting from
  the scan conversion of points, lines,	polygons, and bitmaps, and from	pixel
  draw and copy	operations.  glAlphaFunc does not affect screen	clear
  operations.

Notes

  Alpha	testing	is done	only in	RGBA mode.

Errors

  GL_INVALID_ENUM is generated if func is not an accepted value.

  GL_INVALID_OPERATION is generated if glAlphaFunc is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_ALPHA_TEST_FUNC
  glGet	with argument GL_ALPHA_TEST_REF
  glIsEnabled with argument GL_ALPHA_TEST

See Also

  glBlendFunc, glClear,	glDepthFunc, glEnable, glStencilFunc

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glArrayElementEXT.3gl

Name

  glArrayElementEXT - specify the array	elements used to render	a vertex

C Specification

  void glArrayElementEXT( GLint	i )

Parameters

  i  Specifies an index	in the enabled arrays.

Description

  glArrayElementEXT commands are used within glBegin/glEnd pairs to specify
  vertex and attribute data for	point, line and	polygon	primitives. When
  glArrayElementEXT is called, a single	vertex is drawn, using vertex and
  attribute data taken from location i of the enabled arrays.

  Use glArrayElementEXT	to construct primitives	by indexing vertex data,
  rather than by streaming through arrays of data in first-to-last order.
  Because each call specifies only a single vertex, it is possible to
  explicitly specify per-primitive attributes, such as a single	normal per
  individual triangle.

Notes

  glArrayElementEXT may	be included in display lists. If glArrayElementEXT is
  entered into a display list, the necessary array data	(determined by the
  array	pointers and enables) is also entered into the display list.  Because
  the array pointers and enables are client side state,	their values affect
  display lists	when the lists are created, not	when the lists are executed.

  Static array data may	be read	and cached by the implementation at any	time.
  If static array elements are modified	and the	arrays are not respecified,
  the results of any subsequent	calls to glArrayElementEXT are undefined.

  glArrayElementEXT executes even if GL_VERTEX_ARRAY_EXT is not	enabled.  No
  drawing occurs in this case, but the attributes corresponding	to enabled
  arrays are modified.

  Although it is not an	error to respecify an array between the	execution of
  glBegin and the corresponding	execution of glEnd, the	result of such
  respecification is undefined.

  glArrayElementEXT is part of the EXT_vertex_array extension, not
  part of the core GL command set. If "GL_EXT_vertex_array" is
  included in the string returned by glGetString, when called with argument
  GL_EXTENSIONS, extension EXT_vertex_array is supported.

See Also

  glColorPointerEXT, glDrawArraysEXT, glEdgeFlagPointerEXT, glGetPointervEXT,
  glIndexPointerEXT, glNormalPointerEXT, glTexCoordPointerEXT,
  glVertexPointerEXT

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glBegin.3gl

Name

  glBegin, glEnd - delimit the vertices	of a primitive or a group of like
  primitives

C Specification

  void glBegin(	GLenum mode )

Parameters

  mode	Specifies the primitive	or primitives that will	be created from
    vertices presented between glBegin and the subsequent glEnd.  Ten
    symbolic constants are accepted: GL_POINTS, GL_LINES, GL_LINE_STRIP,
    GL_LINE_LOOP, GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN,
    GL_QUADS, GL_QUAD_STRIP, and GL_POLYGON.

C Specification

  void glEnd( void )

Description

  glBegin and glEnd delimit the	vertices that define a primitive or a group
  of like primitives.  glBegin accepts a single	argument that specifies	which
  of ten ways the vertices are interpreted.  Taking n as an integer count
  starting at one, and N as the	total number of	vertices specified, the
  interpretations are as follows:

  GL_POINTS Treats each	vertex as a single point.  Vertex n defines point n.
        N points are drawn.

  GL_LINES  Treates each pair of vertices as an	independent line segment.
        Vertices 2n-1 and 2n define	line n.	 N/2 lines are drawn.

  GL_LINE_STRIP
        Draws a connected group of line segments from the first vertex to
        the	last.  Vertices	n and n+1 define line n.  N-1 lines are
        drawn.

  GL_LINE_LOOP
        Draws a connected group of line segments from the first vertex to
        the	last, then back	to the first.  Vertices	n and n+1 define line
        n.	The last line, however,	is defined by vertices N and 1.	 N
        lines are drawn.

  GL_TRIANGLES
        Treates each triplet of vertices as	an independent triangle.
        Vertices 3n-2, 3n-1, and 3n	define triangle	n.  N/3	triangles are
        drawn.

  GL_TRIANGLE_STRIP
        Draws a connected group of triangles.  One triangle	is defined
        for	each vertex presented after the	first two vertices.  For odd
        n, vertices	n, n+1,	and n+2	define triangle	n.  For	even n,
        vertices n+1, n, and n+2 define triangle n.	 N-2 triangles are
        drawn.

  GL_TRIANGLE_FAN
        Draws a connected group of triangles.  One triangle	is defined
        for	each vertex presented after the	first two vertices.  Vertices
        1, n+1, and	n+2 define triangle n.	N-2 triangles are drawn.

  GL_QUADS  Treats each	group of four vertices as an independent
        quadrilateral.  Vertices 4n-3, 4n-2, 4n-1, and 4n define
        quadrilateral n.  N/4 quadrilaterals are drawn.

  GL_QUAD_STRIP
        Draws a connected group of quadrilaterals.	One quadrilateral is
        defined for	each pair of vertices presented	after the first	pair.
        Vertices 2n-1, 2n, 2n+2, and 2n+1 define quadrilateral n.  N/2-1
        quadrilaterals are drawn.  Note that the order in which vertices
        are	used to	construct a quadrilateral from strip data is
        different from that	used with independent data.

  GL_POLYGON
        Draws a single, convex polygon.  Vertices 1	through	N define this
        polygon.

  Only a subset	of GL commands can be used between glBegin and glEnd.  The
  commands are glVertex, glColor, glIndex, glNormal, glTexCoord, glEvalCoord,
  glEvalPoint, glMaterial, and glEdgeFlag.  Also, it is	acceptable to use
  glCallList or	glCallLists to execute display lists that include only the
  preceding commands.  If any other GL command is executed between glBegin
  and glEnd, the error flag is set and the command is ignored.

  Regardless of	the value chosen for mode, there is no limit to	the number of
  vertices that	can be defined between glBegin and glEnd.  Lines, triangles,
  quadrilaterals, and polygons that are	incompletely specified are not drawn.
  Incomplete specification results when	either too few vertices	are provided
  to specify even a single primitive or	when an	incorrect multiple of
  vertices is specified. The incomplete	primitive is ignored; the rest are
  drawn.

  The minimum specification of vertices	for each primitive is as follows: 1
  for a	point, 2 for a line, 3 for a triangle, 4 for a quadrilateral, and 3
  for a	polygon.  Modes	that require a certain multiple	of vertices are
  GL_LINES (2),	GL_TRIANGLES (3), GL_QUADS (4),	and GL_QUAD_STRIP (2).

Errors

  GL_INVALID_ENUM is generated if mode is set to an unaccepted value.

  GL_INVALID_OPERATION is generated if a command other than glVertex,
  glColor, glIndex, glNormal, glTexCoord, glEvalCoord, glEvalPoint,
  glMaterial, glEdgeFlag, glCallList, or glCallLists is	executed between
  glBegin and the corresponding	glEnd.

  GL_INVALID_OPERATION is generated if a glBegin is executed between a
  glBegin and the corresponding	execution of glEnd.

  GL_INVALID_OPERATION is generated if glEnd is	executed without being
  preceded by a	glBegin.

See Also

  glCallList, glCallLists, glColor, glEdgeFlag,	glEvalCoord, glEvalPoint,
  glIndex, glMaterial, glNormal, glTexCoord, glVertex

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glBitmap.3gl

Name

  glBitmap - draw a bitmap

C Specification

  void glBitmap( GLsizei width,
         GLsizei height,
         GLfloat xorig,
         GLfloat yorig,
         GLfloat xmove,
         GLfloat ymove,
         const GLubyte *bitmap )

Parameters

  width, height	Specify	the pixel width	and height of the bitmap image.

  xorig, yorig	Specify	the location of	the origin in the bitmap image.	 The
        origin is measured from	the lower left corner of the bitmap,
        with right and up being	the positive axes.

  xmove, ymove	Specify	the x and y offsets to be added	to the current raster
        position after the bitmap is drawn.

  bitmap	Specifies the address of the bitmap image.

Description

  A bitmap is a	binary image.  When drawn, the bitmap is positioned relative
  to the current raster	position, and frame buffer pixels corresponding	to
  ones in the bitmap are written using the current raster color	or index.
  Frame	buffer pixels corresponding to zeros in	the bitmap are not modified.

  glBitmap takes seven arguments.  The first pair specify the width and
  height of the	bitmap image.  The second pair specify the location of the
  bitmap origin	relative to the	lower left corner of the bitmap	image.	The
  third	pair of	arguments specify x and	y offsets to be	added to the current
  raster position after	the bitmap has been drawn.  The	final argument is a
  pointer to the bitmap	image itself.

  The bitmap image is interpreted like image data for the glDrawPixels
  command, with	width and height corresponding to the width and	height
  arguments of that command, and with type set to GL_BITMAP and	format set to
  GL_COLOR_INDEX.  Modes specified using glPixelStore affect the
  interpretation of bitmap image data; modes specified using glPixelTransfer
  do not.

  If the current raster	position is invalid, glBitmap is ignored.  Otherwise,
  the lower left corner	of the bitmap image is positioned at the window
  coordinates

                   x  = floor (x - x )
                w           r   o

                   y  = floor (y - y )
                w           r   o

  where	(x ,y )	is the raster position and (x ,y ) is the bitmap origin.
          r  r                               o  o

  Fragments are then generated for each pixel corresponding to a one in	the
  bitmap image.	 These fragments are generated using the current raster	z
  coordinate, color or color index, and	current	raster texture coordinates.
  They are then	treated	just as	if they	had been generated by a	point, line,
  or polygon, including	texture	mapping, fogging, and all per-fragment
  operations such as alpha and depth testing.

  After	the bitmap has been drawn, the x and y coordinates of the current
  raster position are offset by	xmove and ymove.  No change is made to the z
  coordinate of	the current raster position, or	to the current raster color,
  index, or texture coordinates.

Errors

  GL_INVALID_VALUE is generated	if width or height is negative.

  GL_INVALID_OPERATION is generated if glBitmap	is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_CURRENT_RASTER_POSITION
  glGet	with argument GL_CURRENT_RASTER_COLOR
  glGet	with argument GL_CURRENT_RASTER_INDEX
  glGet	with argument GL_CURRENT_RASTER_TEXTURE_COORDS
  glGet	with argument GL_CURRENT_RASTER_POSITION_VALID

See Also

  glDrawPixels,	glRasterPos, glPixelStore, glPixelTransfer

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glBlendFunc.3gl

Name

  glBlendFunc -	specify	pixel arithmetic

C Specification

  void glBlendFunc( GLenum sfactor,
            GLenum dfactor )

Parameters

  sfactor  Specifies how the red, green, blue, and alpha source blending
       factors are computed.  Nine symbolic	constants are accepted:
       GL_ZERO, GL_ONE, GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR,
       GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA,
       GL_ONE_MINUS_DST_ALPHA, and GL_SRC_ALPHA_SATURATE.

  dfactor  Specifies how the red, green, blue, and alpha destination blending
       factors are computed.  Eight	symbolic constants are accepted:
       GL_ZERO, GL_ONE, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR,
       GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, and
       GL_ONE_MINUS_DST_ALPHA.

Description

  In RGB mode, pixels can be drawn using a function that blends	the incoming
  (source) RGBA	values with the	RGBA values that are already in	the frame
  buffer (the destination values).  By default,	blending is disabled.  Use
  glEnable and glDisable with argument GL_BLEND	to enable and disable
  blending.

  glBlendFunc defines the operation of blending	when it	is enabled.  sfactor
  specifies which of nine methods is used to scale the source color
  components.  dfactor specifies which of eight	methods	is used	to scale the
  destination color components.	 The eleven possible methods are described in
  the table below.  Each method	defines	four scale factors, one	each for red,
  green, blue, and alpha.

  In the table and in subsequent equations, source and destination color
  components are referred to as	(R ,G ,B ,A ) and (R ,G	,B ,A ).  They are
                                  s  s  s  s        d  d  d  d
  understood to	have integer values between zero and (k ,k ,k ,k ), where
                               R  G  B	A
                      mc
                    k  = 2   - 1
                                     c

  and (mR,mG,mB,mA) is the number of red, green, blue, and alpha bitplanes.

  Source and destination scale factors are referred to as (s ,s ,s ,s )	and
                                                            R  G  B  A
  (d ,d ,d ,d ).  The scale factors described in the table, denoted
    R  G  B  A
  (f ,f ,f ,f ), represent either source or destination	factors.  All scale
    R  G  B  A
  factors have range [0,1].

    -----------------------------------------------------------------------
    |     parameter         |            (f ,  f ,  f ,  f )              |
    |                       |              R    G    B    A               |
    -----------------------------------------------------------------------
    |	           	    |		                 		  |
    |	    GL_ZERO	    |		     (0, 0, 0, 0)		  |
    |	           	    |		                 		  |
    |	     GL_ONE	    |		     (1, 1, 1, 1)		  |
    |	           	    |		                 		  |
    |	  GL_SRC_COLOR	    |	     (R	/k , G /k , B /k , A /k	)	  |
    |	           	    |	       s  R   s  G   s  B   s  A	  |
    |	           	    |		                 		  |
    |GL_ONE_MINUS_SRC_COLOR | (1, 1, 1, 1) - (R	/k , G /k , B /k , A /k	) |
    |	           	    |	               s  R   s	 G   s  B   s  A  |
    |	           	    |		                 		  |
    |	  GL_DST_COLOR	    |	     (R	/k , G /k , B /k , A /k	)	  |
    |	           	    |	       d  R   d  G   d  B   d  A	  |
    |	           	    |		                 		  |
    |GL_ONE_MINUS_DST_COLOR | (1, 1, 1, 1) - (R	/k , G /k , B /k , A /k	) |
    |	           	    |	               d  R   d	 G   d  B   d  A  |
    |	           	    |		                 		  |
    |	  GL_SRC_ALPHA	    |	     (A	/k , A /k , A /k , A /k	)	  |
    |	           	    |	       s  R   s  G   s  B   s  A	  |
    |	           	    |		                 		  |
    |	           	    |		                 		  |
    |GL_ONE_MINUS_SRC_ALPHA | (1, 1, 1, 1) - (A	/k , A /k , A /k , A /k	) |
    |	           	    |	               s  R   s  G   s  B   s  A  |
    |	           	    |		                 		  |
    |	           	    |		                 		  |
    |	  GL_DST_ALPHA	    |	     (A	/k , A /k , A /k , A /k	)	  |
    |	           	    |	       d  R   d  G   d  B   d  A	  |
    |	           	    |		                 		  |
    |GL_ONE_MINUS_DST_ALPHA | (1, 1, 1, 1) - (A	/k , A /k , A /k , A /k	) |
    |	           	    |	               d  R   d  G   d  B   d  A  |
    |	           	    |		                 		  |
    |	           	    |		                 		  |
    |GL_SRC_ALPHA_SATURATE  |		     (i,  i,  i,  1)              |
    |	           	    |		                 		  |
    -----------------------------------------------------------------------

  In the table,

                i =	min(A ,	k -A ) / k
                                     s   A  d     A

  To determine the blended RGBA	values of a pixel when drawing in RGB mode,
  the system uses the following	equations:

                  R  = min(k ,  R s +R d )
                               d        R    s R  d R

                  G  = min(k ,  G s +G d )
                               d        G    s G  d G

                  B  = min(k ,  B s +B d )
                               d        B    s B  d B

                  A  = min(k ,  A s +A d )
                               d        A    s A  d A

  Despite the apparent precision of the	above equations, blending arithmetic
  is not exactly specified, because blending operates with imprecise integer
  color	values.	 However, a blend factor that should be	equal to one is
  guaranteed not to modify its multiplicand, and a blend factor	equal to zero
  reduces its multiplicand to zero.  Thus, for example,	when sfactor is
  GL_SRC_ALPHA,	dfactor	is GL_ONE_MINUS_SRC_ALPHA, and A  is equal to k  the
                                                         s              A
  equations reduce to simple replacement:

                      R  = R
                                       d    s

                      G  = G
                                       d    s

                      B  = B
                                       d    s

                      A  = A
                                       d    s

Examples

  Transparency is best implemented using blend function	(GL_SRC_ALPHA,
  GL_ONE_MINUS_SRC_ALPHA) with primitives sorted from farthest to nearest.
  Note that this transparency calculation does not require the presence	of
  alpha	bitplanes in the frame buffer.

  Blend	function (GL_SRC_ALPHA,	GL_ONE_MINUS_SRC_ALPHA)	is also	useful for
  rendering antialiased	points and lines in arbitrary order.

  Polygon antialiasing is optimized using blend	function
  (GL_SRC_ALPHA_SATURATE, GL_ONE) with polygons	sorted from nearest to
  farthest.  (See the glEnable,	glDisable reference page and the
  GL_POLYGON_SMOOTH argument for information on	polygon	antialiasing.)
  Destination alpha bitplanes, which must be present for this blend function
  to operate correctly,	store the accumulated coverage.

Notes

  Incoming (source) alpha is correctly thought of as a material	opacity,
  ranging from 1.0 (K ), representing complete opacity,	to 0.0 (0),
                     A
  representing complete transparency.

  When more than one color buffer is enabled for drawing, blending is done
  separately for each enabled buffer, using for	destination color the
  contents of that buffer.  (See glDrawBuffer.)

  Blending affects only	RGB rendering.	It is ignored by color index
  renderers.

Errors

  GL_INVALID_ENUM is generated if either sfactor or dfactor is not an
  accepted value.

  GL_INVALID_OPERATION is generated if glBlendFunc is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_BLEND_SRC
  glGet	with argument GL_BLEND_DST
  glIsEnabled with argument GL_BLEND

See Also

  glAlphaFunc, glClear,	glDrawBuffer, glEnable,	glLogicOp, glStencilFunc

Introduction | Alphabetic | Specification

Last Edited: Fri, May 26, 1995

AFV


glCallList.3gl

Name

  glCallList - execute a display list

C Specification

  void glCallList( GLuint list )

Parameters

  list	Specifies the integer name of the display list to be executed.

Description

  glCallList causes the	named display list to be executed.  The	commands
  saved	in the display list are	executed in order, just	as if they were
  called without using a display list.	If list	has not	been defined as	a
  display list,	glCallList is ignored.

  glCallList can appear	inside a display list.	To avoid the possibility of
  infinite recursion resulting from display lists calling one another, a
  limit	is placed on the nesting level of display lists	during display-list
  execution.  This limit is at least 64, and it	depends	on the
  implementation.

  GL state is not saved	and restored across a call to glCallList.  Thus,
  changes made to GL state during the execution	of a display list remain
  after	execution of the display list is completed.  Use glPushAttrib,
  glPopAttrib, glPushMatrix, and glPopMatrix to	preserve GL state across
  glCallList calls.

Notes

  Display lists	can be executed	between	a call to glBegin and the
  corresponding	call to	glEnd, as long as the display list includes only
  commands that	are allowed in this interval.

Associated Gets

  glGet	with argument GL_MAX_LIST_NESTING
  glIsList

See Also

  glCallLists, glDeleteLists, glGenLists, glNewList, glPushAttrib,
  glPushMatrix

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glCallLists.3gl

Name

  glCallLists -	execute	a list of display lists

C Specification

  void glCallLists( GLsizei n,
            GLenum type,
            const GLvoid *lists	)

Parameters

  n	 Specifies the number of display lists to be executed.

  type	 Specifies the type of values in lists.	 Symbolic constants GL_BYTE,
     GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT,	GL_INT,
     GL_UNSIGNED_INT, GL_FLOAT, GL_2_BYTES,	GL_3_BYTES, and	GL_4_BYTES
     are accepted.

  lists	 Specifies the address of an array of name offsets in the display
     list.	The pointer type is void because the offsets can be bytes,
     shorts, ints, or floats, depending on the value of type.

Description

  glCallLists causes each display list in the list of names passed as lists
  to be	executed.  As a	result,	the commands saved in each display list	are
  executed in order, just as if	they were called without using a display
  list.	 Names of display lists	that have not been defined are ignored.

  glCallLists provides an efficient means for executing	display	lists.	type
  allows lists with various name formats to be accepted.  The formats are as
  follows:

  GL_BYTE		   lists is treated as an array	of signed bytes, each
               in the range	-128 through 127.

  GL_UNSIGNED_BYTE	   lists is treated as an array	of unsigned bytes,
               each	in the range 0 through 255.

  GL_SHORT		   lists is treated as an array	of signed two-byte
               integers, each in the range -32768 through 32767.

  GL_UNSIGNED_SHORT	   lists is treated as an array	of unsigned two-byte
               integers, each in the range 0 through 65535.

  GL_INT		   lists is treated as an array	of signed four-byte
               integers.

  GL_UNSIGNED_INT	   lists is treated as an array	of unsigned four-byte
               integers.

  GL_FLOAT		   lists is treated as an array	of four-byte
               floating-point values.

  GL_2_BYTES		   lists is treated as an array	of unsigned bytes.
               Each	pair of	bytes specifies	a single display-list
               name.  The value of the pair	is computed as 256
               times the unsigned value of the first byte plus
               the unsigned	value of the second byte.

  GL_3_BYTES		   lists is treated as an array	of unsigned bytes.
               Each	triplet	of bytes specifies a single display-
               list	name.  The value of the	triplet	is computed
               as 65536 times the unsigned value of	the first
               byte, plus 256 times	the unsigned value of the
               second byte,	plus the unsigned value	of the third
               byte.

  GL_4_BYTES		   lists is treated as an array	of unsigned bytes.
               Each	 quadruplet of bytes specifies a single
               display-list	name.  The value of the	quadruplet is
               computed as 16777216	times the unsigned value of
               the first byte, plus	65536 times the	unsigned
               value of the	second byte, plus 256 times the
               unsigned value of the third byte, plus the
               unsigned value of the fourth	byte.

  The list of display list names is not	null-terminated.  Rather, n specifies
  how many names are to	be taken from lists.

  An additional	level of indirection is	made available with the	glListBase
  command, which specifies an unsigned offset that is added to each display-
  list name specified in lists before that display list	is executed.

  glCallLists can appear inside	a display list.	 To avoid the possibility of
  infinite recursion resulting from display lists calling one another, a
  limit	is placed on the nesting level of display lists	during display-list
  execution.  This limit must be at least 64, and it depends on	the
  implementation.

  GL state is not saved	and restored across a call to glCallLists.  Thus,
  changes made to GL state during the execution	of the display lists remain
  after	execution is completed.	 Use glPushAttrib, glPopAttrib,	glPushMatrix,
  and glPopMatrix to preserve GL state across glCallLists calls.

Notes

  Display lists	can be executed	between	a call to glBegin and the
  corresponding	call to	glEnd, as long as the display list includes only
  commands that	are allowed in this interval.

Errors

  GL_INVALID_VALUE is generated	if n is	negative.

  GL_INVALID_ENUM is generated if type is not one of GL_BYTE,
  GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT,
  GL_FLOAT, GL_2_BYTES,	GL_3_BYTES, GL_4_BYTES.

Associated Gets

  glGet	with argument GL_LIST_BASE
  glGet	with argument GL_MAX_LIST_NESTING
  glIsList

See Also

  glCallList, glDeleteLists, glGenLists, glListBase, glNewList,	glPushAttrib,
  glPushMatrix

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glClear.3gl

Name

  glClear - clear buffers to preset values

C Specification

  void glClear(	GLbitfield mask	)

Parameters

  mask	Bitwise	OR of masks that indicate the buffers to be cleared.  The
    four masks are GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT,
    GL_ACCUM_BUFFER_BIT, and GL_STENCIL_BUFFER_BIT.

Description

  glClear sets the bitplane area of the	window to values previously selected
  by glClearColor, glClearIndex, glClearDepth, glClearStencil, and
  glClearAccum.	 Multiple color	buffers	can be cleared simultaneously by
  selecting more than one buffer at a time using glDrawBuffer.

  The pixel ownership test, the	scissor	test, dithering, and the buffer
  writemasks affect the	operation of glClear.  The scissor box bounds the
  cleared region.  Alpha function, blend function, logical operation,
  stenciling, texture mapping, and z-buffering are ignored by glClear.

  glClear takes	a single argument that is the bitwise OR of several values
  indicating which buffer is to	be cleared.

  The values are as follows:

  GL_COLOR_BUFFER_BIT	      Indicates	the buffers currently enabled for
                  color  writing.

  GL_DEPTH_BUFFER_BIT	      Indicates	the depth buffer.

  GL_ACCUM_BUFFER_BIT	      Indicates	the accumulation buffer.

  GL_STENCIL_BUFFER_BIT	      Indicates	the stencil buffer.

  The value to which each buffer is cleared depends on the setting of the
  clear	value for that buffer.

Notes

  If a buffer is not present, then a glClear directed at that buffer has no
  effect.

Errors

  GL_INVALID_VALUE is generated	if any bit other than the four defined bits
  is set in mask.

  GL_INVALID_OPERATION is generated if glClear is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_ACCUM_CLEAR_VALUE
  glGet	with argument GL_DEPTH_CLEAR_VALUE
  glGet	with argument GL_INDEX_CLEAR_VALUE
  glGet	with argument GL_COLOR_CLEAR_VALUE
  glGet	with argument GL_STENCIL_CLEAR_VALUE

See Also

  glClearAccum,	glClearColor, glClearDepth, glClearIndex, glClearStencil,
  glDrawBuffer,	glScissor

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glClearAccum.3gl

Name

  glClearAccum - specify clear values for the accumulation buffer

C Specification

  void glClearAccum( GLfloat red,
             GLfloat green,
             GLfloat blue,
             GLfloat alpha )

Parameters

  red, green, blue, alpha
            Specify	the red, green,	blue, and alpha	values used
            when the accumulation buffer is	cleared.  The default
            values are all zero.

Description

  glClearAccum specifies the red, green, blue, and alpha values	used by
  glClear to clear the accumulation buffer.

  Values specified by glClearAccum are clamped to the range [-1,1].

Errors

  GL_INVALID_OPERATION is generated if glClearAccum is executed	between	the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_ACCUM_CLEAR_VALUE

See Also

  glClear

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glClearColor.3gl

Name

  glClearColor - specify clear values for the color buffers

C Specification

  void glClearColor( GLclampf red,
             GLclampf green,
             GLclampf blue,
             GLclampf alpha )

Parameters

  red, green, blue, alpha
            Specify	the red, green,	blue, and alpha	values used
            when the color buffers are cleared.  The default
            values are all zero.

Description

  glClearColor specifies the red, green, blue, and alpha values	used by
  glClear to clear the color buffers.  Values specified	by glClearColor	are
  clamped to the range [0,1].

Errors

  GL_INVALID_OPERATION is generated if glClearColor is executed	between	the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_COLOR_CLEAR_VALUE

See Also

  glClear

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glClearDepth.3gl

Name

  glClearDepth - specify the clear value for the depth buffer

C Specification

  void glClearDepth( GLclampd depth )

Parameters

  depth	 Specifies the depth value used	when the depth buffer is cleared.

Description

  glClearDepth specifies the depth value used by glClear to clear the depth
  buffer.  Values specified by glClearDepth are	clamped	to the range [0,1].

Errors

  GL_INVALID_OPERATION is generated if glClearDepth is executed	between	the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_DEPTH_CLEAR_VALUE

See Also

  glClear

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glClearIndex.3gl

Name

  glClearIndex - specify the clear value for the color index buffers

C Specification

  void glClearIndex( GLfloat c )

Parameters

  c  Specifies the index used when the color index buffers are cleared.	 The
     default value is zero.

Description

  glClearIndex specifies the index used	by glClear to clear the	color index
  buffers.  c is not clamped.  Rather, c is converted to a fixed-point value
  with unspecified precision to	the right of the binary	point.	The integer
  part of this value is	then masked with 2m-1, where m is the number of	bits
  in a color index stored in the frame buffer.

Errors

  GL_INVALID_OPERATION is generated if glClearIndex is executed	between	the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_INDEX_CLEAR_VALUE
  glGet	with argument GL_INDEX_BITS

See Also

  glClear

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glClearStencil.3gl

Name

  glClearStencil - specify the clear value for the stencil buffer

C Specification

  void glClearStencil( GLint s )

Parameters

  s  Specifies the index used when the stencil buffer is cleared.  The
     default value is zero.

Description

  glClearStencil specifies the index used by glClear to	clear the stencil
  buffer.  s is	masked with 2m-1, where	m is the number	of bits	in the
  stencil buffer.

Errors

  GL_INVALID_OPERATION is generated if glClearStencil is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_STENCIL_CLEAR_VALUE
  glGet	with argument GL_STENCIL_BITS

See Also

  glClear

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glClipPlane.3gl

Name

  glClipPlane -	specify	a plane	against	which all geometry is clipped

C Specification

  void glClipPlane( GLenum plane,
            const GLdouble *equation )

Parameters

  plane	    Specifies which clipping plane is being positioned.	 Symbolic
        names of the form GL_CLIP_PLANEi, where i is an integer between 0
        and	GL_MAX_CLIP_PLANES -1, are accepted.

  equation  Specifies the address of an	array of four double-precision
        floating-point values.  These values are interpreted as a plane
        equation.

Description

  Geometry is always clipped against the boundaries of a six-plane frustum in
  x, y,	and z. glClipPlane allows the specification of additional planes, not
  necessarily perpendicular to the x, y, or z axis, against which all
  geometry is clipped.	Up to GL_MAX_CLIP_PLANES planes	can be specified,
  where	GL_MAX_CLIP_PLANES is at least six in all implementations.  Because
  the resulting	clipping region	is the intersection of the defined half-
  spaces, it is	always convex.

  glClipPlane specifies	a half-space using a four-component plane equation.
  When glClipPlane is called, equation is transformed by the inverse of	the
  modelview matrix and stored in the resulting eye coordinates.	 Subsequent
  changes to the modelview matrix have no effect on the	stored plane-equation
  components.  If the dot product of the eye coordinates of a vertex with the
  stored plane equation	components is positive or zero,	the vertex is in with
  respect to that clipping plane.  Otherwise, it is out.

  Clipping planes are enabled and disabled with	glEnable and glDisable,	and
  called with the argument GL_CLIP_PLANEi, where i is the plane	number.

  By default, all clipping planes are defined as (0,0,0,0) in eye coordinates
  and are disabled.

Notes

  It is	always the case	that GL_CLIP_PLANEi = GL_CLIP_PLANE0 + i.

Errors

  GL_INVALID_ENUM is generated if plane	is not an accepted value.

  GL_INVALID_OPERATION is generated if glClipPlane is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGetClipPlane
  glIsEnabled with argument GL_CLIP_PLANEi

See Also

  glEnable

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glColor.3gl

Name

  glColor3b, glColor3d,	glColor3f, glColor3i, glColor3s, glColor3ub,
  glColor3ui, glColor3us, glColor4b, glColor4d,	glColor4f, glColor4i,
  glColor4s, glColor4ub, glColor4ui, glColor4us, glColor3bv, glColor3dv,
  glColor3fv, glColor3iv, glColor3sv, glColor3ubv, glColor3uiv,	glColor3usv,
  glColor4bv, glColor4dv, glColor4fv, glColor4iv, glColor4sv, glColor4ubv,
  glColor4uiv, glColor4usv - set the current color

C Specification

  void glColor3b( GLbyte red,
          GLbyte green,
          GLbyte blue )
  void glColor3d( GLdouble red,
          GLdouble green,
          GLdouble blue	)
  void glColor3f( GLfloat red,
          GLfloat green,
          GLfloat blue )
  void glColor3i( GLint	red,
          GLint	green,
          GLint	blue )
  void glColor3s( GLshort red,
          GLshort green,
          GLshort blue )
  void glColor3ub( GLubyte red,
           GLubyte green,
           GLubyte blue	)
  void glColor3ui( GLuint red,
           GLuint green,
           GLuint blue )
  void glColor3us( GLushort red,
           GLushort green,
           GLushort blue )
  void glColor4b( GLbyte red,
          GLbyte green,
          GLbyte blue,
          GLbyte alpha )
  void glColor4d( GLdouble red,
          GLdouble green,
          GLdouble blue,
          GLdouble alpha )
  void glColor4f( GLfloat red,
          GLfloat green,
          GLfloat blue,
          GLfloat alpha	)
  void glColor4i( GLint	red,
          GLint	green,
          GLint	blue,
          GLint	alpha )
  void glColor4s( GLshort red,
          GLshort green,
          GLshort blue,
          GLshort alpha	)
  void glColor4ub( GLubyte red,
           GLubyte green,
           GLubyte blue,
           GLubyte alpha )
  void glColor4ui( GLuint red,
           GLuint green,
           GLuint blue,
           GLuint alpha	)
  void glColor4us( GLushort red,
           GLushort green,
           GLushort blue,
           GLushort alpha )

Parameters

  red, green, blue
          Specify new red, green, and blue values for the current
          color.

  alpha		  Specifies a new alpha	value for the current color.
          Included only	in the four-argument glColor4 command.

C Specification

  void glColor3bv( const GLbyte	*v )
  void glColor3dv( const GLdouble *v )
  void glColor3fv( const GLfloat *v )
  void glColor3iv( const GLint *v )
  void glColor3sv( const GLshort *v )
  void glColor3ubv( const GLubyte *v )
  void glColor3uiv( const GLuint *v )
  void glColor3usv( const GLushort *v )
  void glColor4bv( const GLbyte	*v )
  void glColor4dv( const GLdouble *v )
  void glColor4fv( const GLfloat *v )
  void glColor4iv( const GLint *v )
  void glColor4sv( const GLshort *v )
  void glColor4ubv( const GLubyte *v )
  void glColor4uiv( const GLuint *v )
  void glColor4usv( const GLushort *v )

Parameters

  v    Specifies a pointer to an array that contains red, green, blue, and
       (sometimes) alpha values.

Description

  The GL stores	both a current single-valued color index and a current four-
  valued RGBA color.  glColor sets a new four-valued RGBA color.  glColor has
  two major variants: glColor3 and glColor4.  glColor3 variants	specify	new
  red, green, and blue values explicitly, and set the current alpha value to
  1.0 implicitly.  glColor4 variants specify all four color components
  explicitly.

  glColor3b, glColor4b,	glColor3s, glColor4s, glColor3i, and glColor4i take
  three	or four	signed byte, short, or long integers as	arguments.  When v is
  appended to the name,	the color commands can take a pointer to an array of
  such values.

  Current color	values are stored in floating-point format, with unspecified
  mantissa and exponent	sizes.	Unsigned integer color components, when
  specified, are linearly mapped to floating-point values such that the
  largest representable	value maps to 1.0 (full	intensity), and	zero maps to
  0.0 (zero intensity).	 Signed	integer	color components, when specified, are
  linearly mapped to floating-point values such	that the most positive
  representable	value maps to 1.0, and the most	negative representable value
  maps to -1.0.	 Floating-point	values are mapped directly.

  Neither floating-point nor signed integer values are clamped to the range
  [0,1]	before updating	the current color.  However, color components are
  clamped to this range	before they are	interpolated or	written	into a color
  buffer.

Notes

  The current color can	be updated at any time.	 In particular,	glColor	can
  be called between a call to glBegin and the corresponding call to glEnd.

Associated Gets

  glGet	with argument GL_CURRENT_COLOR
  glGet	with argument GL_RGBA_MODE

See Also

  glIndex

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glColorMask.3gl

Name

  glColorMask -	enable and disable writing of frame buffer color components

C Specification

  void glColorMask( GLboolean red,
            GLboolean green,
            GLboolean blue,
            GLboolean alpha )

Parameters

  red, green, blue, alpha
            Specify	whether	red, green, blue, and alpha can	or
            cannot be written into the frame buffer.  The default
            values are all GL_TRUE,	indicating that	the color
            components can be written.

Description

  glColorMask specifies	whether	the individual color components	in the frame
  buffer can or	cannot be written.  If red is GL_FALSE,	for example, no
  change is made to the	red component of any pixel in any of the color
  buffers, regardless of the drawing operation attempted.

  Changes to individual	bits of	components cannot be controlled.  Rather,
  changes are either enabled or	disabled for entire color components.

Errors

  GL_INVALID_OPERATION is generated if glColorMask is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_COLOR_WRITEMASK
  glGet	with argument GL_RGBA_MODE

See Also

  glColor, glIndex, glIndexMask, glDepthMask, glStencilMask

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glColorMaterial.3gl

Name

  glColorMaterial - cause a material color to track the	current	color

C Specification

  void glColorMaterial(	GLenum face,
            GLenum mode )

Parameters

  face	Specifies whether front, back, or both front and back material
    parameters should track	the current color.  Accepted values are
    GL_FRONT, GL_BACK, and GL_FRONT_AND_BACK.  The default value is
    GL_FRONT_AND_BACK.

  mode	Specifies which	of several material parameters track the current
    color.	Accepted values	are GL_EMISSION, GL_AMBIENT, GL_DIFFUSE,
    GL_SPECULAR, and GL_AMBIENT_AND_DIFFUSE.  The default value is
    GL_AMBIENT_AND_DIFFUSE.

Description

  glColorMaterial specifies which material parameters track the	current
  color.  When GL_COLOR_MATERIAL is enabled, the material parameter or
  parameters specified by mode,	of the material	or materials specified by
  face,	track the current color	at all times.	GL_COLOR_MATERIAL is enabled
  and disabled using the commands glEnable and glDisable, called with
  GL_COLOR_MATERIAL as their argument.	By default, it is disabled.

Notes

  glColorMaterial allows a subset of material parameters to be changed for
  each vertex using only the glColor command, without calling glMaterial.  If
  only such a subset of	parameters is to be specified for each vertex,
  glColorMaterial is preferred over calling glMaterial.

  Call glColorMaterial before enabling the GL_COLOR_MATERIAL.

Errors

  GL_INVALID_ENUM is generated if face or mode is not an accepted value.

  GL_INVALID_OPERATION is generated if glColorMaterial is executed between
  the execution	of glBegin and the corresponding execution of glEnd.

Associated Gets

  glIsEnabled with argument GL_COLOR_MATERIAL
  glGet	with argument GL_COLOR_MATERIAL_PARAMETER
  glGet	with argument GL_COLOR_MATERIAL_FACE

See Also

  glColor, glEnable, glLight, glLightModel, glMaterial

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glColorPointerEXT.3gl

Name

  glColorPointerEXT - define a array of	colors

C Specification

  void glColorPointerEXT( GLint	size,
              GLenum type,
              GLsizei stride,
              GLsizei count,
              const	GLvoid *pointer	)

Parameters

  size	   Specifies the number	of components per color. It must be 3 or 4.

  type	   Specifies the data type of each color component in the array.
       Symbolic constants GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT,
       GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT, GL_FLOAT, or
       GL_DOUBLE_EXT, are accepted.

  stride   Specifies the byte offset between consecutive colors.  If stride
       is zero the colors are understood to	be tightly packed in the
       array.

  count	   Specifies the number	of colors, counting from the first, that are
       static.

  pointer  Specifies a pointer to the first component of the first color
       element in the array.

Description

  glColorPointerEXT specifies the location and data format of an array of
  color components to use when rendering using the vertex array extension.
  size specifies the number of components per color, and must be 3 or 4.
  type specifies the data type of each color component and stride gives the
  byte stride from one color to the next allowing vertexes and attributes to
  be packed into a single array or stored in separate arrays. (Single-array
  storage may be more efficient on some implementations.) count indicates
  the number of array elements (counting from the first) that are static.
  Static elements may be modified by the application, but once they are
  modified, the application must explicitly respecify the array before using
  it for any rendering. When a color array is specified, size, type, stride,
  count and pointer are saved as client-side state, and static array elements
  may be cached by the implementation.

  The color array is enabled and disabled using glEnable and glDisable with
  the argument GL_COLOR_ARRAY_EXT. If enabled, the color array is used when
  glDrawArraysEXT or glArrayElementEXT is called.

  Use glDrawArraysEXT to define a sequence of primitives (all of the same
  type) from pre-specified vertex and vertex attribute arrays. Use
  glArrayElementEXT to specify primitives by indexing vertexes and vertex
  attributes.

Notes

  Non-static array elements are not accessed until glArrayElementEXT or
  glDrawArraysEXT is executed.

  By default the color array is disabled and it won't be accessed when
  glArrayElementEXT or glDrawArraysEXT is called.

  Although it is not an error to call glColorPointerEXT between the
  execution of glBegin and the corresponding execution of glEnd, the results
  are undefined.

  glColorPointerEXT will typically be implemented on the client	side with no
  protocol.

  Since	the color array	parameters are client side state, they are not saved
  or restored by glPushAttrib and glPopAttrib.

  glColorPointerEXT commands are not entered into display lists.

  glColorPointerEXT is part of the EXT_vertex_array extension, not
  part of the core GL command set. If "GL_EXT_vertex_array" is
  included in the string returned by glGetString, when called with argument
  GL_EXTENSIONS, extension EXT_vertex_array is supported.

Errors

  GL_INVALID_VALUE is generated	if size	is not 3 or 4.

  GL_INVALID_ENUM is generated if type is not an accepted value.

  GL_INVALID_VALUE is generated	if stride or count is negative.

Associated Gets

  glIsEnabled with argument GL_COLOR_ARRAY_EXT
  glGet	with argument GL_COLOR_ARRAY_SIZE_EXT
  glGet	with argument GL_COLOR_ARRAY_TYPE_EXT
  glGet	with argument GL_COLOR_ARRAY_STRIDE_EXT
  glGet	with argument GL_COLOR_ARRAY_COUNT_EXT
  glGetPointervEXT with	argument GL_COLOR_ARRAY_POINTER_EXT

See Also

  glArrayElementEXT, glDrawArraysEXT, glEdgeFlagPointerEXT, glGetPointervEXT,
  glIndexPointerEXT, glNormalPointerEXT, glTexCoordPointerEXT,
  glVertexPointerEXT, glEnable

Introduction | Alphabetic | Specification

Last Edited: Fri Feb 24, 1995

AFV


glCopyPixels.3gl

Name

  glCopyPixels - copy pixels in	the frame buffer

C Specification

  void glCopyPixels( GLint x,
             GLint y,
             GLsizei width,
             GLsizei height,
             GLenum type )

Parameters

  x, y Specify the window coordinates of the lower left	corner of the
       rectangular region of pixels to be copied.

  width, height
       Specify the dimensions of the rectangular region	of pixels to be
       copied.	Both must be nonnegative.

  type Specifies whether color values, depth values, or	stencil	values are to
       be copied.  Symbolic constants GL_COLOR,	GL_DEPTH, and GL_STENCIL are
       accepted.

Description

  glCopyPixels copies a	screen-aligned rectangle of pixels from	the specified
  frame	buffer location	to a region relative to	the current raster position.
  Its operation	is well	defined	only if	the entire pixel source	region is
  within the exposed portion of	the window.  Results of	copies from outside
  the window, or from regions of the window that are not exposed, are
  hardware dependent and undefined.

  x and	y specify the window coordinates of the	lower left corner of the
  rectangular region to	be copied.  width and height specify the dimensions
  of the rectangular region to be copied.  Both	width and height must not be
  negative.

  Several parameters control the processing of the pixel data while it is
  being	copied.	 These parameters are set with three commands:
  glPixelTransfer, glPixelMap, and glPixelZoom.	 This reference	page
  describes the	effects	on glCopyPixels	of most, but not all, of the
  parameters specified by these	three commands.

  glCopyPixels copies values from each pixel with the lower left-hand corner
  at (x	+ i, y + j) for	0<=i<width  and 0<=j<height.  This pixel is said to be
  the ith pixel	in the jth row.	Pixels are copied in row order from the
  lowest to the	highest	row, left to right in each row.

  type specifies whether color,	depth, or stencil data is to be	copied.	 The
  details of the transfer for each data	type are as follows:

  GL_COLOR	 Indices or RGBA colors	are read from the buffer currently
         specified as the read source buffer (see glReadBuffer).  If
         the GL	is in color index mode,	each index that	is read	from
         this buffer is	converted to a fixed-point format with an
         unspecified number of bits to the right of the	binary point.
         Each index is then shifted left by GL_INDEX_SHIFT bits, and
         added to GL_INDEX_OFFSET.  If GL_INDEX_SHIFT is negative,
         the shift is to the right.  In	either case, zero bits fill
         otherwise unspecified bit locations in	the result.  If
         GL_MAP_COLOR is true, the index is replaced with the value
         that it references in lookup table GL_PIXEL_MAP_I_TO_I.
         Whether the lookup replacement	of the index is	done or	not,
                                   b
         the integer part of the index is then ANDed with 2 -1,	where
         b is the number of bits in a color index buffer.

         If the	GL is in RGBA mode, the	red, green, blue, and alpha
         components of each pixel that is read are converted to	an
         internal floating-point format	with unspecified precision.
         The conversion	maps the largest representable component
         value to 1.0, and component value zero	to 0.0.	 The
         resulting floating-point color	values are then	multiplied by
         GL_c_SCALE and	added to GL_c_BIAS, where c is RED, GREEN,
         BLUE, and ALPHA for the respective color components.  The
         results are clamped to	the range [0,1].  If GL_MAP_COLOR is
         true, each color component is scaled by the size of lookup
         table GL_PIXEL_MAP_c_TO_c, then replaced by the value that
         it references in that table.  c is R, G, B, or	A,
         respectively.

         The resulting indices or RGBA colors are then converted to
         fragments by attaching	the current raster position z
         coordinate and	texture	coordinates to each pixel, then
         assigning window coordinates (x +i,y +j), where (x ,y ) is
                                                r    r             r  r
         the current raster position, and the pixel was	the ith	pixel
         in the	jth row.  These	pixel fragments	are then treated just
         like the fragments generated by rasterizing points, lines,
         or polygons.  Texture mapping,	fog, and all the fragment
         operations are	applied	before the fragments are written to
         the frame buffer.

  GL_DEPTH	 Depth values are read from the	depth buffer and converted
         directly to an	internal floating-point	format with
         unspecified precision.	 The resulting floating-point depth
         value is then multiplied by GL_DEPTH_SCALE and	added to
         GL_DEPTH_BIAS.	 The result is clamped to the range [0,1].

         The resulting depth components	are then converted to
         fragments by attaching	the current raster position color or
         color index and texture coordinates to	each pixel, then
         assigning window coordinates (x +i,y +j), where (x ,y ) is
                                                r    r             r  r
         the current raster position, and the pixel was	the ith	pixel
         in the	jth row.  These	pixel fragments	are then treated just
         like the fragments generated by rasterizing points, lines,
         or polygons.  Texture mapping,	fog, and all the fragment
         operations are	applied	before the fragments are written to
         the frame buffer.

  GL_STENCIL	 Stencil indices are read from the stencil buffer and
         converted to an internal fixed-point format with an
         unspecified number of bits to the right of the	binary point.
         Each fixed-point index	is then	shifted	left by
         GL_INDEX_SHIFT	bits, and added	to GL_INDEX_OFFSET.  If
         GL_INDEX_SHIFT	is negative, the shift is to the right.	 In
         either	case, zero bits	fill otherwise unspecified bit
         locations in the result.  If GL_MAP_STENCIL is	true, the
         index is replaced with	the value that it references in
         lookup	table GL_PIXEL_MAP_S_TO_S.  Whether the	lookup
         replacement of	the index is done or not, the integer part of
                                               b
         the index is then ANDed with 2 -1, where b is the number of
         bits in the stencil buffer.  The resulting stencil indices
         are then written to the stencil buffer	such that the index
         read from the ith location of the jth row is written to
         location (x +i,y +j), where (x	,y ) is	the current raster
                            r    r             r  r
         position.  Only the pixel ownership test, the scissor test,
         and the stencil writemask affect these	writes.

  The rasterization described thus far assumes pixel zoom factors of 1.0.  If
  glPixelZoom is used to change	the x and y pixel zoom factors,	pixels are
  converted to fragments as follows.  If (x , y	) is the current raster
                                           r   r
  position, and	a given	pixel is in the	ith location in	the jth	row of the
  source pixel rectangle, then fragments are generated for pixels whose
  centers are in the rectangle with corners at

                (x +zoom i,	y +zoom	j)
                  r	    x	 r     y

                      and

            (x +zoom (i+1),	y +zoom	(j+1))
              r	x	 r     y

  where	zoom  is the value of GL_ZOOM_X	and zoom  is the value of GL_ZOOM_Y.
        x					y

Examples

  To copy the color pixel in the lower left corner of the window to the
  current raster position, use glCopyPixels(0, 0, 1, 1,	GL_COLOR);

Notes

  Modes	specified by glPixelStore have no effect on the	operation of
  glCopyPixels.

Errors

  GL_INVALID_ENUM is generated if type is not an accepted value.

  GL_INVALID_VALUE is generated	if either width	or height is negative.

  GL_INVALID_OPERATION is generated if type is GL_DEPTH	and there is no	depth
  buffer.

  GL_INVALID_OPERATION is generated if type is GL_STENCIL and there is no
  stencil buffer.

  GL_INVALID_OPERATION is generated if glCopyPixels is executed	between	the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_CURRENT_RASTER_POSITION
  glGet	with argument GL_CURRENT_RASTER_POSITION_VALID

See Also

  glDepthFunc, glDrawBuffer, glDrawPixels, glPixelMap, glPixelTransfer,
  glPixelZoom, glRasterPos, glReadBuffer, glReadPixels,	glStencilFunc

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glCullFace.3gl

Name

  glCullFace - specify whether front- or back-facing facets can	be culled

C Specification

  void glCullFace( GLenum mode )

Parameters

  mode	Specifies whether front- or back-facing	facets are candidates for
    culling.  Symbolic constants GL_FRONT, GL_BACK,	and GL_FRONT_AND_BACK
    are accepted.  The default value is GL_BACK.

Description

  glCullFace specifies whether front- or back-facing facets are	culled (as
  specified by mode) when facet	culling	is enabled.  Facet culling is enabled
  and disabled using the glEnable and glDisable	commands with the argument
  GL_CULL_FACE.	 Facets	include	triangles, quadrilaterals, polygons, and
  rectangles.

  glFrontFace specifies	which of the clockwise and counterclockwise facets
  are front-facing and back-facing.  See glFrontFace.

Notes

  If the mode is GL_FRONT_AND_BACK then	no facets are drawn, but other
  primitives such as points and	lines are.

Errors

  GL_INVALID_ENUM is generated if mode is not an accepted value.

  GL_INVALID_OPERATION is generated if glCullFace is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glIsEnabled with argument GL_CULL_FACE
  glGet	with argument GL_CULL_FACE_MODE

See Also

  glEnable, glFrontFace

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glDeleteLists.3gl

Name

  glDeleteLists	- delete a contiguous group of display lists

C Specification

  void glDeleteLists( GLuint list,
              GLsizei range )

Parameters

  list	 Specifies the integer name of the first display list to delete.

  range	 Specifies the number of display lists to delete.

Description

  glDeleteLists	causes a contiguous group of display lists to be deleted.
  list is the name of the first	display	list to	be deleted, and	range is the
  number of display lists to delete.  All display lists	d with list <= d <=
  list + range - 1 are deleted.

  All storage locations	allocated to the specified display lists are freed,
  and the names	are available for reuse	at a later time.  Names	within the
  range	that do	not have an associated display list are	ignored.  If range is
  zero,	nothing	happens.

Errors

  GL_INVALID_VALUE is generated	if range is negative.

  GL_INVALID_OPERATION is generated if glDeleteLists is	executed between the
  execution of glBegin and the corresponding execution of glEnd.

See Also

  glCallList, glCallLists, glGenLists, glIsList, glNewList

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glDepthFunc.3gl

Name

  glDepthFunc -	specify	the value used for depth buffer	comparisons

C Specification

  void glDepthFunc( GLenum func	)

Parameters

  func	Specifies the depth comparison function.  Symbolic constants
    GL_NEVER, GL_LESS, GL_EQUAL, GL_LEQUAL,	GL_GREATER, GL_NOTEQUAL,
    GL_GEQUAL, and GL_ALWAYS are accepted.	The default value is GL_LESS.

Description

  glDepthFunc specifies	the function used to compare each incoming pixel z
  value	with the z value present in the	depth buffer.  The comparison is
  performed only if depth testing is enabled.  (See glEnable and glDisable of
  GL_DEPTH_TEST.)

  func specifies the conditions	under which the	pixel will be drawn.  The
  comparison functions are as follows:

  GL_NEVER	      Never passes.

  GL_LESS	      Passes if	the incoming z value is	less than the stored
              z	value.

  GL_EQUAL	      Passes if	the incoming z value is	equal to the stored z
              value.

  GL_LEQUAL	      Passes if	the incoming z value is	less than or equal to
              the stored z value.

  GL_GREATER	      Passes if	the incoming z value is	greater	than the
              stored z value.

  GL_NOTEQUAL	      Passes if	the incoming z value is	not equal to the
              stored z value.

  GL_GEQUAL	      Passes if	the incoming z value is	greater	than or	equal
              to the stored z value.

  GL_ALWAYS	      Always passes.

  The default value of func is GL_LESS.	 Initially, depth testing is
  disabled.

Errors

  GL_INVALID_ENUM is generated if func is not an accepted value.

  GL_INVALID_OPERATION is generated if glDepthFunc is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_DEPTH_FUNC
  glIsEnabled with argument GL_DEPTH_TEST

See Also

  glDepthRange,	glEnable

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glDepthMask.3gl

Name

  glDepthMask -	enable or disable writing into the depth buffer

C Specification

  void glDepthMask( GLboolean flag )

Parameters

  flag	Specifies whether the depth buffer is enabled for writing.  If flag
    is zero, depth buffer writing is disabled.  Otherwise, it is enabled.
    Initially, depth buffer	writing	is enabled.

Description

  glDepthMask specifies	whether	the depth buffer is enabled for	writing.  If
  flag is zero,	depth buffer writing is	disabled.  Otherwise, it is enabled.
  Initially, depth buffer writing is enabled.

Errors

  GL_INVALID_OPERATION is generated if glDepthMask is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_DEPTH_WRITEMASK

See Also

  glColorMask, glDepthFunc, glDepthRange, glIndexMask, glStencilMask

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glDepthRange.3gl

Name

  glDepthRange - specify the mapping of	z values from normalized device
  coordinates to window	coordinates

C Specification

  void glDepthRange( GLclampd near,
             GLclampd far )

Parameters

  near	Specifies the mapping of the near clipping plane to window
    coordinates.  The default value	is 0.

  far	Specifies the mapping of the far clipping plane	to window
    coordinates.  The default value	is 1.

Description

  After	clipping and division by w, z coordinates range	from -1.0 to 1.0,
  corresponding	to the near and	far clipping planes.  glDepthRange specifies
  a linear mapping of the normalized z coordinates in this range to window z
  coordinates.	Regardless of the actual depth buffer implementation, window
  coordinate depth values are treated as though	they range from	0.0 through
  1.0 (like color components).	Thus, the values accepted by glDepthRange are
  both clamped to this range before they are accepted.

  The default mapping of 0,1 maps the near plane to 0 and the far plane	to 1.
  With this mapping, the depth buffer range is fully utilized.

Notes

  It is	not necessary that near	be less	than far.  Reverse mappings such as
  1,0 are acceptable.

Errors

  GL_INVALID_OPERATION is generated if glDepthRange is executed	between	the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_DEPTH_RANGE

See Also

  glDepthFunc, glViewport

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glDrawArraysEXT.3gl

Name

  glDrawArraysEXT - render primitives from array data

C Specification

  void glDrawArraysEXT(	GLenum mode,
            GLint first,
            GLsizei	count )

Parameters

  mode	 Specifies what	kind of	primitives to render.  Symbolic	constants
     GL_POINTS, GL_LINE_STRIP, GL_LINE_LOOP, GL_LINES, GL_TRIANGLE_STRIP,
     GL_TRIANGLE_FAN, GL_TRIANGLES,	GL_QUAD_STRIP, GL_QUADS, and
     GL_POLYGON are	accepted.

  first	 Specifies the starting	index in the enabled arrays.

  count	 Specifies the number of indices which should be rendered.

Description

  glDrawArraysEXT makes	it possible to specify multiple	geometric primitives
  with very few	subroutine calls. Instead of calling an	OpenGL procedure to
  pass each individual vertex, normal, or color, separate arrays of vertexes,
  normals, and colors can be prespecified, and used to define a	sequence of
  primitives (all of the same type) with a single call to glDrawArraysEXT.

  When glDrawArraysEXT is called, count	sequential elements from each enabled
  array	are used to construct a	sequence of geometric primitives, beginning
  with element first. mode specifies what kind of primitives are constructed,
  and how the array elements are used to construct these primitives. If
  GL_VERTEX_ARRAY_EXT is not enabled, no geometric primitives are generated.

  Vertex attributes that are modified by glDrawArraysEXT have an unspecified
  value	after glDrawArraysEXT returns.	For example, if	GL_COLOR_ARRAY_EXT is
  enabled, the value of	the current color is undefined after glDrawArraysEXT
  executes.  Attributes	that aren't modified remain well defined.

  Operation of glDrawArraysEXT is atomic with respect to error generation.
  If an	error is generated, no other operations	take place.

Notes

  glDrawArraysEXT may be included in display lists. If glDrawArraysEXT is
  entered into a display list, the necessary array data	(determined by the
  array	pointers and enables) is also entered into the display list.  Because
  the array pointers and enables are client side state,	their values affect
  display lists	when the lists are created, not	when the lists are executed.

  Static array data may	be read	and cached by the implementation at any	time.
  If static array elements are modified	and the	arrays are not respecified,
  the results of any subsequent	calls to glDrawArraysEXT are undefined.

  Although it is not an	error to respecify an array between the	execution of
  glBegin and the corresponding	execution of glEnd, the	result of such
  respecification is undefined.

  glDrawArraysEXT is part of the EXT_vertex_array extension, not
  part of the core GL command set. If "GL_EXT_vertex_array" is
  included in the string returned by glGetString, when called with argument
  GL_EXTENSIONS, extension EXT_vertex_array is supported.

Errors

  GL_INVALID_ENUM is generated if mode is not an accepted value.

  GL_INVALID_VALUE is generated	if count is negative.

  GL_INVALID_OPERATION is generated if glDrawArraysEXT is called between the
  execution of glBegin and the corresponding execution of glEnd.

See Also

  glArrayElementEXT, glColorPointerEXT,	glEdgeFlagPointerEXT,
  glGetPointervEXT, glIndexPointerEXT, glNormalPointerEXT,
  glTexCoordPointerEXT,	glVertexPointerEXT

Introduction | Alphabetic | Specification

Last Edited: Fri Feb 24, 1995

AFV


glDrawBuffer.3gl

Name

  glDrawBuffer - specify which color buffers are to be drawn into

C Specification

  void glDrawBuffer( GLenum mode )

Parameters

  mode	Specifies up to	four color buffers to be drawn into.  Symbolic
    constants GL_NONE, GL_FRONT_LEFT, GL_FRONT_RIGHT, GL_BACK_LEFT,
    GL_BACK_RIGHT, GL_FRONT, GL_BACK, GL_LEFT, GL_RIGHT,
    GL_FRONT_AND_BACK, and GL_AUXi,	where i	is between 0 and
    ``GL_AUX_BUFFERS'' -1, are accepted (GL_AUX_BUFFERS is not the upper
    limit; use glGet to query the number of	available aux buffers.)	The
    default	value is GL_FRONT for single-buffered contexts,	and GL_BACK
    for double-buffered contexts.

Description

  When colors are written to the frame buffer, they are	written	into the
  color	buffers	specified by glDrawBuffer.  The	specifications are as
  follows:

  GL_NONE		   No color buffers are	written.

  GL_FRONT_LEFT		   Only	the front left color buffer is written.

  GL_FRONT_RIGHT	   Only	the front right	color buffer is	written.

  GL_BACK_LEFT		   Only	the back left color buffer is written.

  GL_BACK_RIGHT		   Only	the back right color buffer is written.

  GL_FRONT		   Only	the front left and front right color buffers
               are written.	 If there is no	front right color
               buffer, only	the front left color buffer is
               written.

  GL_BACK		   Only	the back left and back right color buffers
               are written.	 If there is no	back right color
               buffer, only	the back left color buffer is
               written.

  GL_LEFT		   Only	the front left and back	left color buffers
               are written.	 If there is no	back left color
               buffer, only	the front left color buffer is
               written.

  GL_RIGHT		   Only	the front right	and back right color buffers
               are written.	 If there is no	back right color
               buffer, only	the front right	color buffer is
               written.

  GL_FRONT_AND_BACK	   All the front and back color	buffers	(front left,
               front right,	back left, back	right) are written.
               If there are	no back	color buffers, only the	front
               left	and front right	color buffers are written.
               If there are	no right color buffers,	only the
               front left and back left color buffers are
               written.  If	there are no right or back color
               buffers, only the front left	color buffer is
               written.

  GL_AUXi		   Only	auxiliary color	buffer i is written.

  If more than one color buffer	is selected for	drawing, then blending or
  logical operations are computed and applied independently for	each color
  buffer and can produce different results in each buffer.

  Monoscopic contexts include only left	buffers, and stereoscopic contexts
  include both left and	right buffers.	Likewise, single-buffered contexts
  include only front buffers, and double-buffered contexts include both	front
  and back buffers.  The context is selected at	GL initialization.

Notes

  It is	always the case	that GL_AUXi = GL_AUX0 + i.

Errors

  GL_INVALID_ENUM is generated if mode is not an accepted value.

  GL_INVALID_OPERATION is generated if none of the buffers indicated by	mode
  exists.

  GL_INVALID_OPERATION is generated if glDrawBuffer is executed	between	the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_DRAW_BUFFER
  glGet	with argument GL_AUX_BUFFERS

See Also

  glBlendFunc, glColorMask, glIndexMask, glLogicOp, glReadBuffer

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glDrawPixels.3gl

Name

  glDrawPixels - write a block of pixels to the	frame buffer

C Specification

  void glDrawPixels( GLsizei width,
             GLsizei height,
             GLenum format,
             GLenum type,
             const GLvoid *pixels )

Parameters

  width, height	Specify	the dimensions of the pixel rectangle that will	be
        written	into the frame buffer.

  format	Specifies the format of	the pixel data.	 Symbolic constants
        GL_COLOR_INDEX,	GL_STENCIL_INDEX, GL_DEPTH_COMPONENT,
        GL_RGBA, GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_RGB,
        GL_LUMINANCE, and GL_LUMINANCE_ALPHA are accepted.

  type		Specifies the data type	for pixels.  Symbolic constants
        GL_UNSIGNED_BYTE, GL_BYTE, GL_BITMAP, GL_UNSIGNED_SHORT,
        GL_SHORT, GL_UNSIGNED_INT, GL_INT, and GL_FLOAT	are accepted.

  pixels	Specifies a pointer to the pixel data.

Description

  glDrawPixels reads pixel data	from memory and	writes it into the frame
  buffer relative to the current raster	position.  Use glRasterPos to set the
  current raster position, and use glGet with argument
  GL_CURRENT_RASTER_POSITION to	query the raster position.

  Several parameters define the	encoding of pixel data in memory and control
  the processing of the	pixel data before it is	placed in the frame buffer.
  These	parameters are set with	four commands: glPixelStore, glPixelTransfer,
  glPixelMap, and  glPixelZoom.	 This reference	page describes the effects on
  glDrawPixels of many,	but not	all, of	the parameters specified by these
  four commands.

  Data is read from pixels as a	sequence of signed or unsigned bytes, signed
  or unsigned shorts, signed or	unsigned integers, or single-precision
  floating-point values, depending on type.  Each of these bytes, shorts,
  integers, or floating-point values is	interpreted as one color or depth
  component, or	one index, depending on	format.	 Indices are always treated
  individually.	 Color components are treated as groups	of one,	two, three,
  or four values, again	based on format.  Both individual indices and groups
  of components	are referred to	as pixels.  If type is GL_BITMAP, the data
  must be unsigned bytes, and format must be either GL_COLOR_INDEX or
  GL_STENCIL_INDEX.  Each unsigned byte	is treated as eight 1-bit pixels,
  with bit ordering determined by GL_UNPACK_LSB_FIRST (see glPixelStore).

  widthxheight pixels are read from memory, starting at	location pixels.  By
  default, these pixels	are taken from adjacent	memory locations, except that
  after	all width pixels are read, the read pointer is advanced	to the next
  four-byte boundary.  The four-byte row alignment is specified	by
  glPixelStore with argument GL_UNPACK_ALIGNMENT, and it can be	set to one,
  two, four, or	eight bytes.  Other pixel store	parameters specify different
  read pointer advancements, both before the first pixel is read, and after
  all width pixels are read.  Refer to the glPixelStore	reference page for
  details on these options.

  The widthxheight pixels that are read	from memory are	each operated on in
  the same way,	based on the values of several parameters specified by
  glPixelTransfer and glPixelMap.  The details of these	operations, as well
  as the target	buffer into which the pixels are drawn,	are specific to	the
  format of the	pixels,	as specified by	format.	 format	can assume one of
  eleven symbolic values:

  GL_COLOR_INDEX
        Each pixel is a single value, a color index.  It is	converted to
        fixed-point	format,	with an	unspecified number of bits to the
        right of the binary	point, regardless of the memory	data type.
        Floating-point values convert to true fixed-point values.  Signed
        and	unsigned integer data is converted with	all fraction bits set
        to zero.  Bitmap data convert to either 0.0	or 1.0.

        Each fixed-point index is then shifted left	by GL_INDEX_SHIFT
        bits and added to GL_INDEX_OFFSET.	If GL_INDEX_SHIFT is
        negative, the shift	is to the right.  In either case, zero bits
        fill otherwise unspecified bit locations in	the result.

        If the GL is in RGBA mode, the resulting index is converted	to an
        RGBA pixel using the GL_PIXEL_MAP_I_TO_R, GL_PIXEL_MAP_I_TO_G,
        GL_PIXEL_MAP_I_TO_B, and GL_PIXEL_MAP_I_TO_A tables.  If the GL
        is in color	index mode, and	if GL_MAP_COLOR	is true, the index is
        replaced with the value that it references in lookup table
        GL_PIXEL_MAP_I_TO_I.  Whether the lookup replacement of the	index
        is done or not, the	integer	part of	the index is then ANDed	with
             b
        2 -1, where	b is the number	of bits	in a color index buffer.

        The	resulting indices or RGBA colors are then converted to
        fragments by attaching the current raster position z coordinate
        and	texture	coordinates to each pixel, then	assigning x and	y
        window coordinates to the nth fragment such	that

                 x  = x	 + n mod width
                  n    r

                        y  = y  + floor (n/width)
                   n	r

        where (x ,y	) is the current raster	position.  These pixel
                    r  r
        fragments are then treated just like the fragments generated by
        rasterizing	points,	lines, or polygons.  Texture mapping, fog,
        and	all the	fragment operations are	applied	before the fragments
        are	written	to the frame buffer.

  GL_STENCIL_INDEX
        Each pixel is a single value, a stencil index.  It is converted
        to fixed-point format, with	an unspecified number of bits to the
        right of the binary	point, regardless of the memory	data type.
        Floating-point values convert to true fixed-point values.  Signed
        and	unsigned integer data is converted with	all fraction bits set
        to zero.  Bitmap data convert to either 0.0	or 1.0.

        Each fixed-point index is then shifted left	by GL_INDEX_SHIFT
        bits, and added to GL_INDEX_OFFSET.	 If GL_INDEX_SHIFT is
        negative, the shift	is to the right.  In either case, zero bits
        fill otherwise unspecified bit locations in	the result.  If
        GL_MAP_STENCIL is true, the	index is replaced with the value that
        it references in lookup table GL_PIXEL_MAP_S_TO_S.	Whether	the
        lookup replacement of the index is done or not, the	integer	part
                                             b
        of the index is then ANDed with 2 -1, where	b is the number	of
        bits in the	stencil	buffer.	 The resulting stencil indices are
        then written to the	stencil	buffer such that the nth index is
        written to location

                   x  = x  + n mod width
                n    r

                   y  = y  + floor (n/width)
                n    r

       where (x	,y ) is	the current raster position.  Only the pixel
               r  r
       ownership test, the scissor test, and the stencil writemask affect
       these writes.

  GL_DEPTH_COMPONENT
       Each pixel is a single-depth component.	Floating-point data is
       converted directly to an	internal floating-point	format with
       unspecified precision.  Signed integer data is mapped linearly to the
       internal	floating-point format such that	the most positive
       representable integer value maps	to 1.0,	and the	most negative
       representable value maps	to -1.0.  Unsigned integer data	is mapped
       similarly: the largest integer value maps to 1.0, and zero maps to
       0.0.  The resulting floating-point depth	value is then multiplied by
       GL_DEPTH_SCALE and added	to GL_DEPTH_BIAS.  The result is clamped to
       the range [0,1].

       The resulting depth components are then converted to fragments by
       attaching the current raster position color or color index and texture
       coordinates to each pixel, then assigning x and y window	coordinates
       to the nth fragment such	that

                   x  = x  + n mod width
                n    r

                   y  = y  + floor (n/width)
                n    r

       where (x	,y ) is	the current raster position.  These pixel fragments
               r  r
       are then treated	just like the fragments	generated by rasterizing
       points, lines, or polygons.  Texture mapping, fog, and all the
       fragment	operations are applied before the fragments are	written	to
       the frame buffer.

  GL_RGBA
       Each pixel is a four-component group: red first,	followed by green,
       followed	by blue, followed by alpha.  Floating-point values are
       converted directly to an	internal floating-point	format with
       unspecified precision.  Signed integer values are mapped	linearly to
       the internal floating-point format such that the	most positive
       representable integer value maps	to 1.0,	and the	most negative
       representable value maps	to -1.0.  Unsigned integer data	is mapped
       similarly: the largest integer value maps to 1.0, and zero maps to
       0.0.  The resulting floating-point color	values are then	multiplied by
       GL_c_SCALE and added to GL_c_BIAS, where	c is RED, GREEN, BLUE, and
       ALPHA for the respective	color components.  The results are clamped to
       the range [0,1].

       If GL_MAP_COLOR is true,	each color component is	scaled by the size of
       lookup table GL_PIXEL_MAP_c_TO_c, then replaced by the value that it
       references in that table.  c is R, G, B,	or A, respectively.

       The resulting RGBA colors are then converted to fragments by attaching
       the current raster position z coordinate	and texture coordinates	to
       each pixel, then	assigning x and	y window coordinates to	the nth
       fragment	such that

                   x  = x  + n mod width
                n    r

                   y  = y  + floor (n/width)
                n    r

       where (x	,y ) is	the current raster position.  These pixel fragments
               r  r
       are then treated	just like the fragments	generated by rasterizing
       points, lines, or polygons.  Texture mapping, fog, and all the
       fragment	operations are applied before the fragments are	written	to
       the frame buffer.

  GL_RED
       Each pixel is a single red component.  This component is	converted to
       the internal floating-point format in the same way as the red
       component of an RGBA pixel is, then it is converted to an RGBA pixel
       with green and blue set to 0.0, and alpha set to	1.0.  After this
       conversion, the pixel is	treated	just as	if it had been read as an
       RGBA pixel.

  GL_GREEN
       Each pixel is a single green component.	This component is converted
       to the internal floating-point format in	the same way as	the green
       component of an RGBA pixel is, then it is converted to an RGBA pixel
       with red	and blue set to	0.0, and alpha set to 1.0.  After this
       conversion, the pixel is	treated	just as	if it had been read as an
       RGBA pixel.

  GL_BLUE
       Each pixel is a single blue component.  This component is converted to
       the internal floating-point format in the same way as the blue
       component of an RGBA pixel is, then it is converted to an RGBA pixel
       with red	and green set to 0.0, and alpha	set to 1.0.  After this
       conversion, the pixel is	treated	just as	if it had been read as an
       RGBA pixel.

  GL_ALPHA
       Each pixel is a single alpha component.	This component is converted
       to the internal floating-point format in	the same way as	the alpha
       component of an RGBA pixel is, then it is converted to an RGBA pixel
       with red, green,	and blue set to	0.0.  After this conversion, the
       pixel is	treated	just as	if it had been read as an RGBA pixel.

  GL_RGB
       Each pixel is a three-component group: red first, followed by green,
       followed	by blue.  Each component is converted to the internal
       floating-point format in	the same way as	the red, green,	and blue
       components of an	RGBA pixel are.	 The color triple is converted to an
       RGBA pixel with alpha set to 1.0.  After	this conversion, the pixel is
       treated just as if it had been read as an RGBA pixel.

  GL_LUMINANCE
       Each pixel is a single luminance	component.  This component is
       converted to the	internal floating-point	format in the same way as the
       red component of	an RGBA	pixel is, then it is converted to an RGBA
       pixel with red, green, and blue set to the converted luminance value,
       and alpha set to	1.0.  After this conversion, the pixel is treated
       just as if it had been read as an RGBA pixel.

  GL_LUMINANCE_ALPHA
       Each pixel is a two-component group: luminance first, followed by
       alpha.  The two components are converted	to the internal	floating-
       point format in the same	way as the red component of an RGBA pixel is,
       then they are converted to an RGBA pixel	with red, green, and blue set
       to the converted	luminance value, and alpha set to the converted	alpha
       value.  After this conversion, the pixel	is treated just	as if it had
       been read as an RGBA pixel.

  The following	table summarizes the meaning of	the valid constants for	the
  type parameter:

     -------------------------------------------------------------
     |      type        |          corresponding type            |
     -------------------------------------------------------------
     |GL_UNSIGNED_BYTE  |	      unsigned 8-bit integer	     |
     |     GL_BYTE	    |	       signed 8-bit integer	     |
     |    GL_BITMAP	    | single bits in unsigned 8-bit integers |
     |GL_UNSIGNED_SHORT |	     unsigned 16-bit integer	     |
     |    GL_SHORT	    |	      signed 16-bit integer	     |
     | GL_UNSIGNED_INT  |	     unsigned 32-bit integer	     |
     |     GL_INT	    |		  32-bit integer	     |
     |    GL_FLOAT	    |	 single-precision floating-point     |
     -------------------------------------------------------------

  The rasterization described thus far assumes pixel zoom factors of 1.0.  If
  glPixelZoom is used to change	the x and y pixel zoom factors,	pixels are
  converted to fragments as follows.  If (x , y ) is the current raster
                                           r   r
  position, and	a given	pixel is in the	nth column and mth row of the pixel
  rectangle, then fragments are	generated for pixels whose centers are in the
  rectangle with corners at

                  (x + zoom  n, y + zoom  m)
                                r      x     r      y

                      (x + zoom  (n+1), y + zoom  (m+1))
                            r      x         r      y

  where	zoom  is the value of GL_ZOOM_X	and zoom  is the value of GL_ZOOM_Y.
            x                                   y

Errors

  GL_INVALID_VALUE is generated	if either width	or height is negative.

  GL_INVALID_ENUM is generated if format or type is not	one of the accepted
  values.

  GL_INVALID_OPERATION is generated if format is GL_RED, GL_GREEN, GL_BLUE,
  GL_ALPHA, GL_RGB, GL_RGBA, GL_LUMINANCE, or GL_LUMINANCE_ALPHA, and the GL
  is in	color index mode.

  GL_INVALID_ENUM is generated if type is GL_BITMAP and	format is not either
  GL_COLOR_INDEX or GL_STENCIL_INDEX.

  GL_INVALID_OPERATION is generated if format is GL_STENCIL_INDEX and there
  is no	stencil	buffer.

  GL_INVALID_OPERATION is generated if glDrawPixels is executed	between	the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_CURRENT_RASTER_POSITION
  glGet	with argument GL_CURRENT_RASTER_POSITION_VALID

See Also

  glAlphaFunc, glBlendFunc, glCopyPixels, glDepthFunc, glLogicOp, glPixelMap,
  glPixelStore,	glPixelTransfer, glPixelZoom, glRasterPos, glReadPixels,
  glScissor, glStencilFunc

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glEdgeFlag.3gl

Name

  glEdgeFlag, glEdgeFlagv - flag edges as either boundary or nonboundary

C Specification

  void glEdgeFlag( GLboolean flag )

Parameters

  flag	Specifies the current edge flag	value, either true or false.

C Specification

  void glEdgeFlagv( const GLboolean *flag )

Parameters

  flag Specifies a pointer to an array that contains a single Boolean
       element,	which replaces the current edge	flag value.

Description

  Each vertex of a polygon, separate triangle, or separate quadrilateral
  specified between a glBegin/glEnd pair is marked as the start	of either a
  boundary or nonboundary edge.	 If the	current	edge flag is true when the
  vertex is specified, the vertex is marked as the start of a boundary edge.
  Otherwise, the vertex	is marked as the start of a nonboundary	edge.
  glEdgeFlag sets the edge flag	to true	if flag	is nonzero, false otherwise.

  The vertices of connected triangles and connected quadrilaterals are always
  marked as boundary, regardless of the	value of the edge flag.

  Boundary and nonboundary edge	flags on vertices are significant only if
  GL_POLYGON_MODE is set to GL_POINT or	GL_LINE.  See glPolygonMode.

  Initially, the edge flag bit is true.

Notes

  The current edge flag	can be updated at any time.  In	particular,
  glEdgeFlag can be called between a call to glBegin and the corresponding
  call to glEnd.

Associated Gets

  glGet	with argument GL_EDGE_FLAG

See Also

  glBegin, glPolygonMode

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glEdgeFlagPointerEXT.3gl

Name

  glEdgeFlagPointerEXT - define	an array of edge flags

C Specification

  void glEdgeFlagPointerEXT( GLsizei stride,
                 GLsizei count,
                 const GLboolean *pointer )

Parameters

  stride   Specifies the byte offset between consecutive edge flags.  If
       stride is zero the edge flags are understood	to be tightly packed
       in the array.

  count	   Specifies the number	of edge	flags, counting	from the first,	that
       are static.

  pointer  Specifies a pointer to the first edge flag in the array.

Description

  glEdgeFlagPointerEXT specifies the location and data format of an array of
  boolean edge flags to use when rendering when using the vertex array
  extension. stride gives the byte stride from one edge flag to the next
  allowing vertexes and attributes to be packed into a single array or stored
  in separate arrays.  (Single-array storage may be more efficient on some
  implementations.) count indicates the number of array elements (counting
  from the first) that are static. Static elements may be modified by the
  application, but once they are modified, the application must explicitly
  respecify the array before using it for any rendering. When an edge flag
  array is specified, stride, count and pointer are saved as client-side state,
  and static array elements may be cached by the implementation.

  The edge flag	array is enabled and disabled using glEnable and glDisable
  with the argument GL_EDGE_FLAG_ARRAY_EXT. If enabled,	the edge flag array
  is used when glDrawArraysEXT or glArrayElementEXT is called.

  Use glDrawArraysEXT to define	a sequence of primitives (all of the same
  type)	from pre-specified vertex and vertex attribute arrays.	Use
  glArrayElementEXT to specify primitives by indexing vertexes and vertex
  attributes.

Notes

  Non-static array elements are	not accessed until glArrayElementEXT or
  glDrawArraysEXT is executed.

  By default the edge flag array is disabled and it won't be accessed when
  glArrayElementEXT or glDrawArraysEXT is called.

  Although it is not an error to call glEdgeFlagPointerEXT between the
  execution of glBegin and the corresponding execution of glEnd, the results
  are undefined.

  glEdgeFlagPointerEXT will typically be implemented on	the client side	with
  no protocol.

  Since	the edge flag array parameters are client side state, they are not
  saved	or restored by glPushAttrib and	glPopAttrib.

  glEdgeFlagPointerEXT commands	are not	entered	into display lists.

  glEdgeFlagPointerEXT is part of the EXT_vertex_array extension,
  not part of the core GL command set. If "GL_EXT_vertex_array" is
  included in the string returned by glGetString, when called with argument
  GL_EXTENSIONS, extension EXT_vertex_array is supported.

Errors

  GL_INVALID_ENUM is generated if stride  or count is negative.

Associated Gets

  glIsEnabled with argument GL_EDGE_FLAG_ARRAY_EXT
  glGet	with argument GL_EDGE_FLAG_ARRAY_STRIDE_EXT
  glGet	with argument GL_EDGE_FLAG_ARRAY_COUNT_EXT
  glGetPointervEXT with	argument GL_EDGE_FLAG_ARRAY_POINTER_EXT

See Also

  glArrayElementEXT, glColorPointerEXT,	glDrawArraysEXT, glGetPointervEXT,
  glIndexPointerEXT, glNormalPointerEXT, glTexCoordPointerEXT,
  glVertexPointerEXT, glEnable

Introduction | Alphabetic | Specification

Last Edited: Fri Feb 24, 1995

AFV


glEnable.3gl

Name

  glEnable, glDisable -	enable or disable GL capabilities

C Specification

  void glEnable( GLenum	cap )

Parameters

  cap  Specifies a symbolic constant indicating	a GL capability.

C Specification

  void glDisable( GLenum cap )

Parameters

  cap  Specifies a symbolic constant indicating	a GL capability.

Description

  glEnable and glDisable enable	and disable various capabilities.  Use
  glIsEnabled or glGet to determine the	current	setting	of any capability.

  Both glEnable	and glDisable take a single argument, cap, which can assume
  one of the following values:

  GL_ALPHA_TEST		   If enabled, do alpha	testing. See glAlphaFunc.

  GL_AUTO_NORMAL	   If enabled, compute surface normal vectors
               analytically	when either GL_MAP2_VERTEX_3 or
               GL_MAP2_VERTEX_4 is used to generate	vertices.
               See glMap2.

  GL_BLEND		   If enabled, blend the incoming RGBA color values
               with	the values in the color	buffers.  See
               glBlendFunc.

  GL_CLIP_PLANEi	   If enabled, clip geometry against user-defined
               clipping plane i.  See glClipPlane.

  GL_COLOR_ARRAY_EXT	   If enabled, colors are taken	from the color array
               when	glArrayElementEXT or glDrawArraysEXT is
               called.  See	glColorPointerEXT, glArrayElementEXT
               and glDrawArraysEXT.

  GL_COLOR_MATERIAL	   If enabled, have one	or more	material parameters
               track the current color.  See glColorMaterial.

  GL_CULL_FACE		   If enabled, cull polygons based on their winding
               in window coordinates.  See glCullFace.

  GL_DEPTH_TEST		   If enabled, do depth	comparisons and	update the
               depth buffer.  See glDepthFunc and  glDepthRange.

  GL_DITHER		   If enabled, dither color components or indices
               before they are written to the color	buffer.

  GL_EDGE_FLAG_ARRAY_EXT   If enabled, edge flags are taken from the edge
               flags array when glArrayElementEXT or
               glDrawArraysEXT is called.  See
               glEdgeFlagPointerEXT, glArrayElementEXT and
               glDrawArraysEXT.

  GL_FOG		   If enabled, blend a fog color into the
               posttexturing color.	 See glFog.

  GL_INDEX_ARRAY_EXT	   If enabled, color indexes are taken from the	color
               index array when glArrayElementEXT or
               glDrawArraysEXT is called.  See glIndexPointerEXT,
               glArrayElementEXT and glDrawArraysEXT.

  GL_LIGHTi		   If enabled, include light i in the evaluation of
               the lighting	equation.  See glLightModel and
               glLight.

  GL_LIGHTING		   If enabled, use the current lighting	parameters to
               compute the vertex color or index.  Otherwise,
               simply associate the	current	color or index with
               each	vertex.	 See glMaterial, glLightModel, and
               glLight.

  GL_LINE_SMOOTH	   If enabled, draw lines with correct filtering.
               Otherwise, draw aliased lines.  See glLineWidth.

  GL_LINE_STIPPLE	   If enabled, use the current line stipple pattern
               when	drawing	lines.	See glLineStipple.

  GL_LOGIC_OP		   If enabled, apply the currently selected logical
               operation to	the incoming and color buffer
               indices.  See glLogicOp.

  GL_MAP1_COLOR_4	   If enabled, calls to	glEvalCoord1, glEvalMesh1,
               and glEvalPoint1 will generate RGBA values.	See
               glMap1.

  GL_MAP1_INDEX		   If enabled, calls to	glEvalCoord1, glEvalMesh1,
               and glEvalPoint1 will generate color	indices.  See
               glMap1.

  GL_MAP1_NORMAL	   If enabled, calls to	glEvalCoord1, glEvalMesh1,
               and glEvalPoint1 will generate normals.  See
               glMap1.

  GL_MAP1_TEXTURE_COORD_1  If enabled, calls to	glEvalCoord1, glEvalMesh1,
               and glEvalPoint1 will generate s texture
               coordinates.	 See glMap1.

  GL_MAP1_TEXTURE_COORD_2  If enabled, calls to	glEvalCoord1, glEvalMesh1,
               and glEvalPoint1 will generate s and	t texture
               coordinates.	 See glMap1.

  GL_MAP1_TEXTURE_COORD_3  If enabled, calls to	glEvalCoord1, glEvalMesh1,
               and glEvalPoint1 will generate s, t,	and r texture
               coordinates.	 See glMap1.

  GL_MAP1_TEXTURE_COORD_4  If enabled, calls to	glEvalCoord1, glEvalMesh1,
               and glEvalPoint1 will generate s, t,	r, and q
               texture coordinates.	 See glMap1.

  GL_MAP1_VERTEX_3	   If enabled, calls to	glEvalCoord1, glEvalMesh1,
               and glEvalPoint1 will generate will generate	x, y,
               and z vertex	coordinates.  See glMap1.

  GL_MAP1_VERTEX_4	   If enabled, calls to	glEvalCoord1, glEvalMesh1,
               and glEvalPoint1 will generate homogeneous x, y,
               z, and w vertex coordinates.	 See glMap1.

  GL_MAP2_COLOR_4	   If enabled, calls to	glEvalCoord2, glEvalMesh2,
               and glEvalPoint2 will generate RGBA values.	See
               glMap2.

  GL_MAP2_INDEX		   If enabled, calls to	glEvalCoord2, glEvalMesh2,
               and glEvalPoint2 will generate color	indices.  See
               glMap2.

  GL_MAP2_NORMAL	   If enabled, calls to	glEvalCoord2, glEvalMesh2,
               and glEvalPoint2 will generate normals.  See
               glMap2.

  GL_MAP2_TEXTURE_COORD_1  If enabled, calls to	glEvalCoord2, glEvalMesh2,
               and glEvalPoint2 will generate s texture
               coordinates.	 See glMap2.

  GL_MAP2_TEXTURE_COORD_2  If enabled, calls to	glEvalCoord2, glEvalMesh2,
               and glEvalPoint2 will generate s and	t texture
               coordinates.	 See glMap2.

  GL_MAP2_TEXTURE_COORD_3  If enabled, calls to	glEvalCoord2, glEvalMesh2,
               and glEvalPoint2 will generate s, t,	and r texture
               coordinates.	 See glMap2.

  GL_MAP2_TEXTURE_COORD_4  If enabled, calls to	glEvalCoord2, glEvalMesh2,
               and glEvalPoint2 will generate s, t,	r, and q
               texture coordinates.	 See glMap2.

  GL_MAP2_VERTEX_3	   If enabled, calls to	glEvalCoord2, glEvalMesh2,
               and glEvalPoint2 will generate will generate	x, y,
               and z vertex	coordinates.  See glMap2.

  GL_MAP2_VERTEX_4	   If enabled, calls to	glEvalCoord2, glEvalMesh2,
               and glEvalPoint2 will generate homogeneous x, y,
               z, and w vertex coordinates.	 See glMap2.

  GL_NORMAL_ARRAY_EXT	   If enabled, normals are taken from the normal
               array when glArrayElementEXT	or glDrawArraysEXT is
               called.  See	glNormalPointerEXT, glArrayElementEXT
               and glDrawArraysEXT.

  GL_NORMALIZE		   If enabled, normal vectors specified	with glNormal
               are scaled to unit length after transformation.
               See glNormal.

  GL_POINT_SMOOTH	   If enabled, draw points with	proper filtering.
               Otherwise, draw aliased points.  See	glPointSize.

  GL_POLYGON_SMOOTH	   If enabled, draw polygons with proper filtering.
               Otherwise, draw aliased polygons.  See
               glPolygonMode.

  GL_POLYGON_STIPPLE	   If enabled, use the current polygon stipple
               pattern when	rendering polygons.  See
               glPolygonStipple.

  GL_SCISSOR_TEST	   If enabled, discard fragments that are outside the
               scissor rectangle.  See glScissor.

  GL_STENCIL_TEST	   If enabled, do stencil testing and update the
               stencil buffer.  See	glStencilFunc and
               glStencilOp.

  GL_TEXTURE_1D		   If enabled, one-dimensional texturing is performed
               (unless two-dimensional texturing is	also
               enabled).  See glTexImage1D.

  GL_TEXTURE_2D		   If enabled, two-dimensional texturing is
               performed.  See glTexImage2D.

  GL_TEXTURE_COORD_ARRAY_EXT
               If enabled, texture coordinates are taken from the
               texture coordinates array when glArrayElementEXT
               or glDrawArraysEXT is called.  See
               glTexCoordPointerEXT, glArrayElementEXT and
               glDrawArraysEXT.

  GL_TEXTURE_GEN_Q	   If enabled, the q texture coordinate	is computed
               using the texture generation	function defined with
               glTexGen.  Otherwise, the current q texture
               coordinate is used.	See glTexGen.

  GL_TEXTURE_GEN_R	   If enabled, the r texture coordinate	is computed
               using the texture generation	function defined with
               glTexGen.  Otherwise, the current r texture
               coordinate is used.	See glTexGen.

  GL_TEXTURE_GEN_S	   If enabled, the s texture coordinate	is computed
               using the texture generation	function defined with
               glTexGen.  Otherwise, the current s texture
               coordinate is used.	See glTexGen.

  GL_TEXTURE_GEN_T	   If enabled, the t texture coordinate	is computed
               using the texture generation	function defined with
               glTexGen.  Otherwise, the current t texture
               coordinate is used.	See glTexGen.

  GL_VERTEX_ARRAY_EXT	   If enabled, vertexes	are taken from the vertex
               array when glArrayElementEXT	or glDrawArraysEXT is
               called.  See	glVertexPointerEXT, glArrayElementEXT
               and glDrawArraysEXT.

Errors

  GL_INVALID_ENUM is generated if cap is not one of the	values listed above.

  GL_INVALID_OPERATION is generated if glEnable	or glDisable is	executed
  between the execution	of  glBegin and	the corresponding execution of glEnd.

See Also

  glAlphaFunc, glBlendFunc, glClipPlane, glColorMaterial, glCullFace,
  glDepthFunc, glDepthRange, glFog, glGet, glIsEnabled,	glLight,
  glLightModel,	glLineWidth, glLineStipple, glLogicOp, glMap1, glMap2,
  glMaterial, glNormal,	glPointSize, glPolygonMode, glPolygonStipple,
  glScissor, glStencilFunc, glStencilOp, glTexGen, glTexImage1D, glTexImage2D

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glEvalCoord.3gl

Name

  glEvalCoord1d, glEvalCoord1f,	glEvalCoord2d, glEvalCoord2f, glEvalCoord1dv,
  glEvalCoord1fv, glEvalCoord2dv, glEvalCoord2fv - evaluate enabled one- and
  two-dimensional maps

C Specification

  void glEvalCoord1d( GLdouble u )
  void glEvalCoord1f( GLfloat u	)
  void glEvalCoord2d( GLdouble u,
              GLdouble v )
  void glEvalCoord2f( GLfloat u,
              GLfloat v	)

Parameters

  u  Specifies a value that is the domain coordinate u to the basis function
     defined in	a previous glMap1 or glMap2 command.

  v  Specifies a value that is the domain coordinate v to the basis function
     defined in	a previous glMap2 command.  This argument is not present in
     an	glEvalCoord1 command.

C Specification

  void glEvalCoord1dv( const GLdouble *u )
  void glEvalCoord1fv( const GLfloat *u	)
  void glEvalCoord2dv( const GLdouble *u )
  void glEvalCoord2fv( const GLfloat *u	)

Parameters

  u    Specifies a pointer to an array containing either one or	two domain
       coordinates.  The first coordinate is u.	 The second coordinate is v,
       which is	present	only in	glEvalCoord2 versions.

Description

  glEvalCoord1 evaluates enabled one-dimensional maps at argument u.
  glEvalCoord2 does the	same for two-dimensional maps using two	domain
  values, u and	v.  Maps are defined with glMap1 and glMap2 and	enabled	and
  disabled with	 glEnable and glDisable.

  When one of the glEvalCoord commands is issued, all currently	enabled	maps
  of the indicated dimension are evaluated.  Then, for each enabled map, it
  is as	if the corresponding GL	command	was issued with	the computed value.
  That is, if GL_MAP1_INDEX or GL_MAP2_INDEX is	enabled, a glIndex command is
  simulated.  If GL_MAP1_COLOR_4 or GL_MAP2_COLOR_4 is enabled,	a glColor
  command is simulated.	 If GL_MAP1_NORMAL or GL_MAP2_NORMAL is	enabled, a
  normal vector	is produced, and if any	of GL_MAP1_TEXTURE_COORD_1,
  GL_MAP1_TEXTURE_COORD_2, GL_MAP1_TEXTURE_COORD_3, GL_MAP1_TEXTURE_COORD_4,
  GL_MAP2_TEXTURE_COORD_1, GL_MAP2_TEXTURE_COORD_2, GL_MAP2_TEXTURE_COORD_3,
  or GL_MAP2_TEXTURE_COORD_4 is	enabled, then an appropriate glTexCoord
  command is simulated.

  The GL uses evaluated	values instead of current values for those
  evaluations that are enabled,	and current values otherwise, for color,
  color	index, normal, and texture coordinates.	 However, the evaluated
  values do not	update the current values.  Thus, if glVertex commands are
  interspersed with glEvalCoord	commands, the color, normal, and texture
  coordinates associated with the glVertex commands are	not affected by	the
  values generated by the glEvalCoord commands,	but rather only	by the most
  recent glColor, glIndex, glNormal, and glTexCoord commands.

  No commands are issued for maps that are not enabled.	 If more than one
  texture evaluation is	enabled	for a particular dimension (for	example,
  GL_MAP2_TEXTURE_COORD_1 and GL_MAP2_TEXTURE_COORD_2),	then only the
  evaluation of	the map	that produces the larger number	of coordinates (in
  this case, GL_MAP2_TEXTURE_COORD_2) is carried out.  GL_MAP1_VERTEX_4
  overrides GL_MAP1_VERTEX_3, and GL_MAP2_VERTEX_4 overrides
  GL_MAP2_VERTEX_3, in the same	manner.	 If neither a three- nor four-
  component vertex map is enabled for the specified dimension, the
  glEvalCoord command is ignored.

  If automatic normal generation is enabled, by	calling	glEnable with
  argument GL_AUTO_NORMAL, glEvalCoord2	generates surface normals
  analytically,	regardless of the contents or enabling of the GL_MAP2_NORMAL
  map.	Let

                                        Dp   Dp
                    m = -- · --
                                        Du   Dv

  where D represents the partial differential operator.

  Then the generated normal n is

                           m
                                    n =  -----
                         ||m||

  If automatic normal generation is disabled, the corresponding	normal map
  GL_MAP2_NORMAL, if enabled, is used to produce a normal.  If neither
  automatic normal generation nor a normal map is enabled, no normal is
  generated for	glEvalCoord2 commands.

Associated Gets

  glIsEnabled with argument GL_MAP1_VERTEX_3
  glIsEnabled with argument GL_MAP1_VERTEX_4
  glIsEnabled with argument GL_MAP1_INDEX
  glIsEnabled with argument GL_MAP1_COLOR_4
  glIsEnabled with argument GL_MAP1_NORMAL
  glIsEnabled with argument GL_MAP1_TEXTURE_COORD_1
  glIsEnabled with argument GL_MAP1_TEXTURE_COORD_2
  glIsEnabled with argument GL_MAP1_TEXTURE_COORD_3
  glIsEnabled with argument GL_MAP1_TEXTURE_COORD_4
  glIsEnabled with argument GL_MAP2_VERTEX_3
  glIsEnabled with argument GL_MAP2_VERTEX_4
  glIsEnabled with argument GL_MAP2_INDEX
  glIsEnabled with argument GL_MAP2_COLOR_4
  glIsEnabled with argument GL_MAP2_NORMAL
  glIsEnabled with argument GL_MAP2_TEXTURE_COORD_1
  glIsEnabled with argument GL_MAP2_TEXTURE_COORD_2
  glIsEnabled with argument GL_MAP2_TEXTURE_COORD_3
  glIsEnabled with argument GL_MAP2_TEXTURE_COORD_4
  glIsEnabled with argument GL_AUTO_NORMAL
  glGetMap

See Also

  glBegin, glColor, glEnable, glEvalMesh, glEvalPoint, glIndex,	glMap1,
  glMap2, glMapGrid, glNormal, glTexCoord, glVertex

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glEvalMesh.3gl

Name

  glEvalMesh1, glEvalMesh2 - compute a one- or two-dimensional grid of points
  or lines

C Specification

  void glEvalMesh1( GLenum mode,
            GLint i1,
            GLint i2 )

Parameters

  mode	In glEvalMesh1,	specifies whether to compute a one-dimensional mesh
    of points or lines.  Symbolic constants	GL_POINT and GL_LINE are
    accepted.

  i1, i2
    Specify	the first and last integer values for grid domain variable i.

C Specification

  void glEvalMesh2( GLenum mode,
            GLint i1,
            GLint i2,
            GLint j1,
            GLint j2 )

Parameters

  mode In glEvalMesh2, specifies whether to compute a two-dimensional mesh of
       points, lines, or polygons.  Symbolic constants GL_POINT, GL_LINE, and
       GL_FILL are accepted.

  i1, i2
       Specify the first and last integer values for grid domain variable i.

  j1, j2
       Specify the first and last integer values for grid domain variable j.

Description

  glMapGrid and	glEvalMesh are used in tandem to efficiently generate and
  evaluate a series of evenly spaced map domain	values.	 glEvalMesh steps
  through the integer domain of	a one- or two-dimensional grid,	whose range
  is the domain	of the evaluation maps specified by glMap1 and glMap2.	mode
  determines whether the resulting vertices are	connected as points, lines,
  or filled polygons.

  In the one-dimensional case, glEvalMesh1, the	mesh is	generated as if	the
  following code fragment were executed:

    glBegin(type);
    for (i = i1; i <= i2; i += 1)
        glEvalCoord1(i · du + u1)
    glEnd();

  where

       du = (u2-u1)/n

  and n, u1, and u2 are	the arguments to the most recent glMapGrid1 command.
  type is GL_POINT if mode is GL_POINT, or GL_LINES if	mode is	GL_LINE.  The
  one absolute numeric requirement is that if i	= n, then the value computed
  from i·du + u1 is exactly u2.

  In the two-dimensional case, glEvalMesh2, let

             du = (u2-u1)/n

             dv = (v2-v1)/m

  where	n, u1, u2, m, v1, and v2 are the arguments to the most recent
  glMapGrid2 comand.  Then, if mode is	GL_FILL, the glEvalMesh2 command is
  equivalent to:

  for (j = j1;  j < j2; j += 1) {
      glBegin(GL_QUAD_STRIP);
      for (i = i1; i <=	i2; i += 1) {
      glEvalCoord2(i·du + u1, j·dv + v1);
      glEvalCoord2(i·du + u1, (j+1)·dv + v1);
      }
      glEnd();
  }

  If mode is GL_LINE, then a call to glEvalMesh2 is equivalent to:

  for (j = j1;  j <= j2; j += 1) {
      glBegin(GL_LINE_STRIP);
      for (i = i1; i <= i2; i += 1)
      glEvalCoord2(i·du + u1, j·dv + v1);
      glEnd();
  }
  for (i = i1; i <= i2; i += 1) {
      glBegin(GL_LINE_STRIP);
      for (j = j1; j <= j1; j += 1)
      glEvalCoord2(i·du + u1, j·dv + v1);
      glEnd();
  }

  And finally, if mode is GL_POINT, then a call	to glEvalMesh2 is equivalent
  to:

  glBegin(GL_POINTS);
  for (j = j1; j <= j2; j += 1) {
      for (i = i1; i <= i2; i += 1) {
      glEvalCoord2(i·du + u1, j·dv + v1);
      }
  }
  glEnd();

  In all three cases, the only absolute	numeric	requirements are that if
  i = n, then the value	computed from i·du + u1	is exactly u2, and if j	= m,
  then the value computed from j·dv + v1 is exactly v2.

Errors

  GL_INVALID_ENUM is generated if mode is not an accepted value.

  GL_INVALID_OPERATION is generated if glEvalMesh is called between a call to
  glBegin and the corresponding	call to	glEnd.

Associated Gets

  glGet	with argument GL_MAP1_GRID_DOMAIN
  glGet	with argument GL_MAP2_GRID_DOMAIN
  glGet	with argument GL_MAP1_GRID_SEGMENTS
  glGet	with argument GL_MAP2_GRID_SEGMENTS

See Also

  glBegin, glEvalCoord,	glEvalPoint, glMap1, glMap2, glMapGrid

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glEvalPoint.3gl

Name

  glEvalPoint1,	glEvalPoint2 - generate	and evaluate a single point in a mesh

C Specification

  void glEvalPoint1( GLint i )
  void glEvalPoint2( GLint i,
             GLint j )

Parameters

  i  Specifies the integer value for grid domain variable i.

  j  Specifies the integer value for grid domain variable j (glEvalPoint2
     only).

Description

  glMapGrid and	glEvalMesh are used in tandem to efficiently generate and
  evaluate a series of evenly spaced map domain	values.	 glEvalPoint can be
  used to evaluate a single grid point in the same gridspace that is
  traversed by glEvalMesh.  Calling glEvalPoint1 is equivalent to calling

               glEvalCoord1(i·du + u );
                                                1
                    where

                du = (u	-u )/n
                       2  1

  and n, u , and u  are	the arguments to the most recent glMapGrid1 command.
          1       2
  The one absolute numeric requirement is that if i = n, then the value
  computed from	i·du + u  is exactly u .
            1	      2

  In the two-dimensional case, glEvalPoint2, let

                 du=(u -u )/n
                      2	 1
                 dv=(v -v )/m
                      2	 1
  where	n, u , u , m, v	, and v	 are the arguments to the most recent
            1   2      1       2
  glMapGrid2 command.  Then the glEvalPoint2 command is	equivalent to calling

              glEvalCoord2(i·du	+ u , j·dv + v );
                           1          1

  The only absolute numeric requirements are that if i = n, then the value
  computed from	i·du + u  is exactly u , and if	j = m, then the	value
                        1             2
  computed from	j·dv + v  is exactly v .
            1	      2

Associated Gets

  glGet	with argument GL_MAP1_GRID_DOMAIN
  glGet	with argument GL_MAP2_GRID_DOMAIN
  glGet	with argument GL_MAP1_GRID_SEGMENTS
  glGet	with argument GL_MAP2_GRID_SEGMENTS

See Also

  glEvalCoord, glEvalMesh, glMap1, glMap2, glMapGrid

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glFeedbackBuffer.3gl

Name

  glFeedbackBuffer - controls feedback mode

C Specification

  void glFeedbackBuffer( GLsizei size,
             GLenum	type,
             GLfloat *buffer )

Parameters

  size	  Specifies the	maximum	number of values that can be written into
      buffer.

  type	  Specifies a symbolic constant	that describes the information that
      will be returned for each vertex.  GL_2D, GL_3D, GL_3D_COLOR,
      GL_3D_COLOR_TEXTURE, and GL_4D_COLOR_TEXTURE are accepted.

  buffer  Returns the feedback data.

Description

  The glFeedbackBuffer function	controls feedback.  Feedback, like selection,
  is a GL mode.	 The mode is selected by calling glRenderMode with
  GL_FEEDBACK.	When the GL is in feedback mode, no pixels are produced	by
  rasterization.  Instead, information about primitives	that would have	been
  rasterized is	fed back to the	application using the GL.

  glFeedbackBuffer has three arguments:	buffer is a pointer to an array	of
  floating-point values	into which feedback information	is placed.  size
  indicates the	size of	the array.  type is a symbolic constant	describing
  the information that is fed back for each vertex.  glFeedbackBuffer must be
  issued before	feedback mode is enabled (by calling glRenderMode with
  argument GL_FEEDBACK).  Setting GL_FEEDBACK without establishing the
  feedback buffer, or calling glFeedbackBuffer while the GL is in feedback
  mode,	is an error.

  The GL is taken out of feedback mode by calling glRenderMode with a
  parameter value other	than GL_FEEDBACK.  When	this is	done while the GL is
  in feedback mode, glRenderMode returns the number of entries placed in the
  feedback array.  The returned	value never exceeds size. If the feedback
  data required	more room than was available in	buffer,	glRenderMode returns
  a negative value.

  While	in feedback mode, each primitive that would be rasterized generates a
  block	of values that get copied into the feedback array.  If doing so	would
  cause	the number of entries to exceed	the maximum, the block is partially
  written so as	to fill	the array (if there is any room	left at	all), and an
  overflow flag	is set.	 Each block begins with	a code indicating the
  primitive type, followed by values that describe the primitive's vertices
  and associated data.	Entries	are also written for bitmaps and pixel
  rectangles.  Feedback	occurs after polygon culling and glPolyMode
  interpretation of polygons has taken place, so polygons that are culled are
  not returned in the feedback buffer.	It can also occur after	polygons with
  more than three edges	are broken up into triangles, if the GL
  implementation renders polygons by performing	this decomposition.

  The glPassThrough command can	be used	to insert a marker into	the feedback
  buffer.  See glPassThrough.

  Following is the grammar for the blocks of values written into the feedback
  buffer.  Each	primitive is indicated with a unique identifying value
  followed by some number of vertices.	Polygon	entries	include	an integer
  value	indicating how many vertices follow.  A	vertex is fed back as some
  number of floating-point values, as determined by type.  Colors are fed
  back as four values in RGBA mode and one value in color index	mode.

       feedbackList <- feedbackItem feedbackList | feedbackItem

       feedbackItem <- point | lineSegment | polygon | bitmap |
       pixelRectangle |	passThru

       point <-	GL_POINT_TOKEN vertex

       lineSegment <- GL_LINE_TOKEN vertex vertex | GL_LINE_RESET_TOKEN
       vertex vertex

       polygon <- GL_POLYGON_TOKEN n polySpec

       polySpec	<- polySpec vertex | vertex vertex vertex

       bitmap <- GL_BITMAP_TOKEN vertex

       pixelRectangle <- GL_DRAW_PIXEL_TOKEN vertex | GL_COPY_PIXEL_TOKEN
       vertex

       passThru	<- GL_PASS_THROUGH_TOKEN value

       vertex <- 2d | 3d | 3dColor | 3dColorTexture | 4dColorTexture

       2d <- value value

       3d <- value value value

       3dColor	<- value value value color

       3dColorTexture <- value value value color tex

       4dColorTexture <- value value value value color tex

       color <-	rgba | index

       rgba <- value value value value

       index <-	value

       tex <- value value value	value

  value	is a floating-point number, and	n is a floating-point integer giving
  the number of	vertices in the	polygon.  GL_POINT_TOKEN, GL_LINE_TOKEN,
  GL_LINE_RESET_TOKEN, GL_POLYGON_TOKEN, GL_BITMAP_TOKEN,
  GL_DRAW_PIXEL_TOKEN, GL_COPY_PIXEL_TOKEN and GL_PASS_THROUGH_TOKEN are
  symbolic floating-point constants.  GL_LINE_RESET_TOKEN is returned
  whenever the line stipple pattern is reset.  The data	returned as a vertex
  depends on the feedback type.

  The following	table gives the	correspondence between type and	the number of
  values per vertex.  k	is 1 in	color index mode and 4 in RGBA mode.

------------------------------------------------------------------------------
|       type         | coordinates | color | texture | total number of values |
------------------------------------------------------------------------------
|	GL_2D	     |	  x, y	   |	   |	     |		 2	      |
|	GL_3D	     |	 x, y, z   |	   |	     |		 3	      |
|    GL_3D_COLOR     |	 x, y, z   |   k   |	     |		3+k	      |
|GL_3D_COLOR_TEXTURE |	x, y, z,   |   k   |	4    |		7+k	      |
|GL_4D_COLOR_TEXTURE | x, y, z,	w  |   k   |	4    |		8+k	      |
------------------------------------------------------------------------------

  Feedback vertex coordinates are in window coordinates, except	w, which is
  in clip coordinates.	Feedback colors	are lighted, if	lighting is enabled.
  Feedback texture coordinates are generated, if texture coordinate
  generation is	enabled.  They are always transformed by the texture matrix.

Notes

  glFeedbackBuffer, when used in a display list, is not	compiled into the
  display list but rather is executed immediately.

Errors

  GL_INVALID_ENUM is generated if type is not an accepted value.

  GL_INVALID_VALUE is generated	if size	is negative.

  GL_INVALID_OPERATION is generated if glFeedbackBuffer	is called while	the
  render mode is GL_FEEDBACK, or if glRenderMode is called with	argument
  GL_FEEDBACK before glFeedbackBuffer is called	at least once.

  GL_INVALID_OPERATION is generated if glFeedbackBuffer	is executed between
  the execution	of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_RENDER_MODE

See Also

  glBegin, glLineStipple, glPassThrough, glPolygonMode,	glRenderMode,
  glSelectBuffer

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glFinish.3gl

Name

  glFinish - block until all GL	execution is complete

C Specification

  void glFinish( void )

Description

  glFinish does	not return until the effects of	all previously called GL
  commands are complete.  Such effects include all changes to GL state,	all
  changes to connection	state, and all changes to the frame buffer contents.

Notes

  glFinish requires a round trip to the	server.

Errors

  GL_INVALID_OPERATION is generated if glFinish	is executed between the
  execution of	glBegin	and the	corresponding execution	of glEnd.

See Also

  glFlush, glXWaitGL, glXWaitX

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glFlush.3gl

Name

  glFlush - force execution of GL commands in finite time

C Specification

  void glFlush(	void )

Description

  Different GL implementations buffer commands in several different
  locations, including network buffers and the graphics	accelerator itself.
  glFlush empties all of these buffers,	causing	all issued commands to be
  executed as quickly as they are accepted by the actual rendering engine.
  Though this execution	may not	be completed in	any particular time period,
  it does complete in finite time.

  Because any GL program might be executed over	a network, or on an
  accelerator that buffers commands, all programs should call glFlush
  whenever they	count on having	all of their previously	issued commands
  completed.  For example, call	glFlush	before waiting for user	input that
  depends on the generated image.

Notes

  glFlush can return at	any time.  It does not wait until the execution	of
  all previously issued	OpenGL commands	is complete.

Errors

  GL_INVALID_OPERATION is generated if glFlush is executed between the
  execution of	glBegin	and the	corresponding execution	of glEnd.

See Also

  glFinish

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glFog.3gl

Name

  glFogf, glFogi, glFogfv, glFogiv - specify fog parameters

C Specification

  void glFogf( GLenum pname,
           GLfloat param )
  void glFogi( GLenum pname,
           GLint param )

Parameters

  pname	  Specifies a single-valued fog	parameter.  GL_FOG_MODE,
      GL_FOG_DENSITY, GL_FOG_START,	GL_FOG_END, and	GL_FOG_INDEX are
      accepted.

  param	  Specifies the	value that pname will be set to.

C Specification

  void glFogfv(	GLenum pname,
        const GLfloat *params )
  void glFogiv(	GLenum pname,
        const GLint *params )

Parameters

  pname
       Specifies a fog parameter.  GL_FOG_MODE,	GL_FOG_DENSITY,	GL_FOG_START,
       GL_FOG_END, GL_FOG_INDEX, and GL_FOG_COLOR are accepted.

  params
       Specifies the value or values to	be assigned to pname.  GL_FOG_COLOR
       requires	an array of four values.  All other parameters accept an
       array containing	only a single value.

Description

  Fog is enabled and disabled with glEnable and	glDisable using	the argument
  GL_FOG.  While enabled, fog affects rasterized geometry, bitmaps, and	pixel
  blocks, but not buffer clear operations.

  glFog	assigns	the value or values in params to the fog parameter specified
  by pname.  The accepted values for pname are as follows:

  GL_FOG_MODE	      params is	a single integer or floating-point value that
              specifies	the equation to	be used	to compute the fog
              blend factor, f.	Three symbolic constants are
              accepted:	GL_LINEAR, GL_EXP, and GL_EXP2.	 The
              equations	corresponding to these symbolic	constants are
              defined below.  The default fog mode is GL_EXP.

  GL_FOG_DENSITY      params is	a single integer or floating-point value that
              specifies	density, the fog density used in both
              exponential fog equations.  Only nonnegative densities
              are accepted.  The default fog density is	1.0.

  GL_FOG_START	      params is	a single integer or floating-point value that
              specifies	start, the near	distance used in the linear
              fog equation.  The default near distance is 0.0.

  GL_FOG_END	      params is	a single integer or floating-point value that
              specifies	end, the far distance used in the linear fog
              equation.	 The default far distance is 1.0.

  GL_FOG_INDEX	      params is	a single integer or floating-point value that
              specifies	i , the	fog color index.  The default fog
                                 f
              index is 0.0.

  GL_FOG_COLOR	      params contains four integer or floating-point values
              that specify C , the fog color.  Integer values are
                                    f
              mapped linearly such that	the most positive
              representable value maps to 1.0, and the most negative
              representable value maps to -1.0.	 Floating-point
              values are mapped	directly.  After conversion, all
              color components are clamped to the range	[0,1].	The
              default fog color	is (0,0,0,0).

  Fog blends a fog color with each rasterized pixel fragment's posttexturing
  color	using a	blending factor	f.  Factor f is	computed in one	of three
  ways,	depending on the fog mode.  Let	z be the distance in eye coordinates
  from the origin to the fragment being	fogged.	 The equation for GL_LINEAR
  fog is

                  end - z
            f = -----------
                end	- start

  The equation for GL_EXP fog is

                     -(density · z)
                        f = e

  The equation for GL_EXP2 fog is

                                           2
                 -(density · z)
                f = e

  Regardless of	the fog	mode, f	is clamped to the range	[0,1] after it is
  computed.  Then, if the GL is	in RGBA	color mode, the	fragment's color Cr
  is replaced by

                   C' = fC +(1-f)C
                                r     r       f

  In color index mode, the fragment's color index i  is	replaced by
                                                   r

                   i' = i +(1-f)i
                                r    r       f

Errors

  GL_INVALID_ENUM is generated if pname	is not an accepted value, or if	pname
  is GL_FOG_MODE and params is not an accepted value.

  GL_INVALID_VALUE is generated	if pname is GL_FOG_DENSITY and params is
  negative.

  GL_INVALID_OPERATION is generated if glFog is	called between a call to
  glBegin and the corresponding	call to	glEnd.

Associated Gets

  glIsEnabled with argument GL_FOG
  glGet	with argument GL_FOG_COLOR
  glGet	with argument GL_FOG_INDEX
  glGet	with argument GL_FOG_DENSITY
  glGet	with argument GL_FOG_START
  glGet	with argument GL_FOG_END
  glGet	with argument GL_FOG_MODE

See Also

  glEnable

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glFrontFace.3gl

Name

  glFrontFace -	define front- and back-facing polygons

C Specification

  void glFrontFace( GLenum mode	)

Parameters

  mode	Specifies the orientation of front-facing polygons.  GL_CW and GL_CCW
    are accepted.  The default value is GL_CCW.

Description

  In a scene composed entirely of opaque closed	surfaces, back-facing
  polygons are never visible.  Eliminating these invisible polygons has	the
  obvious benefit of speeding up the rendering of the image.  Elimination of
  back-facing polygons is enabled and disabled with glEnable and glDisable
  using	argument GL_CULL_FACE.

  The projection of a polygon to window	coordinates is said to have clockwise
  winding if an	imaginary object following the path from its first vertex,
  its second vertex, and so on,	to its last vertex, and	finally	back to	its
  first	vertex,	moves in a clockwise direction about the interior of the
  polygon.  The	polygon's winding is said to be	counterclockwise if the
  imaginary object following the same path moves in a counterclockwise
  direction about the interior of the polygon.	glFrontFace specifies whether
  polygons with	clockwise winding in window coordinates, or counterclockwise
  winding in window coordinates, are taken to be front-facing.	Passing
  GL_CCW to mode selects counterclockwise polygons as front-facing; GL_CW
  selects clockwise polygons as	front-facing.  By default, counterclockwise
  polygons are taken to	be front-facing.

Errors

  GL_INVALID_ENUM is generated if mode is not an accepted value.

  GL_INVALID_OPERATION is generated if glFrontFace is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_FRONT_FACE

See Also

  glCullFace, glLightModel

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glFrustum.3gl

Name

  glFrustum - multiply the current matrix by a perspective matrix

C Specification

  void glFrustum( GLdouble left,
          GLdouble right,
          GLdouble bottom,
          GLdouble top,
          GLdouble near,
          GLdouble far )

Parameters

  left,	right Specify the coordinates for the left and right vertical
          clipping planes.

  bottom, top Specify the coordinates for the bottom and top horizontal
          clipping planes.

  near,	far   Specify the distances to the near	and far	depth clipping
          planes.  Both distances must be positive.

Description

  glFrustum describes a	perspective matrix that	produces a perspective
  projection.  (left, bottom, -near) and (right, top,  -near) specify the
  points on the	near clipping plane that are mapped to the lower left and
  upper	right corners of the window, respectively, assuming that the eye is
  located at (0, 0, 0).	 -far specifies	the location of	the far	clipping
  plane.  Both near and	far must be positive.  The corresponding matrix	is

           |					   |
           |    2·near  			   |
                   |  ----------                           |
           |  right-left       0       A       0   |
           |					   |
           |	  0	    2·near     B       0   |
           |              ----------               |
           |		  top-bottom		   |
           |	  0	       0       C       D   |
           |					   |
           |	  0	       0       -1      0   |

                    right+left
                A = ----------
                    right-left

                    top+bottom
                B = ----------
                    top-bottom

                      far+near
                C = - --------
                      far-near

                      2·far·near
                D = - ----------
                       far-near

  The current matrix is	multiplied by this matrix with the result replacing
  the current matrix.  That is,	if M is	the current matrix and F is the
  frustum perspective matrix, then M is	replaced with M·F.

  Use glPushMatrix and glPopMatrix to save and restore the current matrix
  stack.

Notes

  Depth	buffer precision is affected by	the values specified for near and
  far.	The greater the	ratio of far to	near is, the less effective the	depth
  buffer will be at distinguishing between surfaces that are near each other.
  If

                       far
                   r = ----
                       near

  roughly log  r bits of depth buffer precision are lost.  Because r
             2
  approaches infinity as near approaches zero, near must never be set to
  zero.

Errors

  GL_INVALID_VALUE is generated	if near	or far is not positive.

  GL_INVALID_OPERATION is generated if glFrustum is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_MATRIX_MODE
  glGet	with argument GL_MODELVIEW_MATRIX
  glGet	with argument GL_PROJECTION_MATRIX
  glGet	with argument GL_TEXTURE_MATRIX

See Also

  glOrtho, glMatrixMode, glMultMatrix, glPushMatrix, glViewport

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glGenLists.3gl

Name

  glGenLists - generate	a contiguous set of empty display lists

C Specification

  GLuint glGenLists( GLsizei range )

Parameters

  range	 Specifies the number of contiguous empty display lists	to be
     generated.

Description

  glGenLists has one argument, range.  It returns an integer n such that
  range	contiguous empty display lists,	named n, n+1, ..., n+range -1, are
  created.  If range is	zero, if there is no group of range contiguous names
  available, or	if any error is	generated, no display lists are	generated,
  and zero is returned.

Errors

  GL_INVALID_VALUE is generated	if range is negative.

  GL_INVALID_OPERATION is generated if glGenLists is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glIsList

See Also

  glCallList, glCallLists, glDeleteLists, glNewList

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glGet.3gl

Name

  glGetBooleanv, glGetDoublev, glGetFloatv, glGetIntegerv - return the value
  or values of a selected parameter

C Specification

  void glGetBooleanv( GLenum pname,
              GLboolean	*params	)

C Specification

  void glGetDoublev( GLenum pname,
             GLdouble *params )

C Specification

  void glGetFloatv( GLenum pname,
            GLfloat *params )

C Specification

  void glGetIntegerv( GLenum pname,
              GLint *params )

Parameters

  pname	  Specifies the	parameter value	to be returned.	 The symbolic
      constants in the list	below are accepted.

  params  Returns the value or values of the specified parameter.

Description

  These	four commands return values for	simple state variables in GL. pname
  is a symbolic	constant indicating the	state variable to be returned, and
  params is a pointer to an array of the indicated type	in which to place the
  returned data.

  Type conversion is performed if params has a different type than the state
  variable value being requested.  If glGetBooleanv is called, a floating-
  point	or integer value is converted to GL_FALSE if and only if it is zero.
  Otherwise, it	is converted to	GL_TRUE.  If glGetIntegerv is called, Boolean
  values are returned as GL_TRUE or GL_FALSE, and most floating-point values
  are rounded to the nearest integer value.  Floating-point colors and
  normals, however, are	returned with a	linear mapping that maps 1.0 to	the
  most positive	representable integer value, and -1.0 to the most negative
  representable	integer	value.	If glGetFloatv or glGetDoublev is called,
  Boolean values are returned as GL_TRUE or GL_FALSE, and integer values are
  converted to floating-point values.

  The following	symbolic constants are accepted	by pname:

  GL_ACCUM_ALPHA_BITS	   params returns one value, the number	of alpha
               bitplanes in	the accumulation buffer.

  GL_ACCUM_BLUE_BITS	   params returns one value, the number	of blue
               bitplanes in	the accumulation buffer.

  GL_ACCUM_CLEAR_VALUE	   params returns four values: the red,	green, blue,
               and alpha values used to clear the accumulation
               buffer.  Integer values, if requested, are
               linearly mapped from	the internal floating-point
               representation such that 1.0	returns	the most
               positive representable integer value, and -1.0
               returns the most negative representable integer
               value.  See glClearAccum.

  GL_ACCUM_GREEN_BITS	   params returns one value, the number	of green
               bitplanes in	the accumulation buffer.

  GL_ACCUM_RED_BITS	   params returns one value, the number	of red
               bitplanes in	the accumulation buffer.

  GL_ALPHA_BIAS		   params returns one value, the alpha bias factor
               used	during pixel transfers.	 See glPixelTransfer.

  GL_ALPHA_BITS		   params returns one value, the number	of alpha
               bitplanes in	each color buffer.

  GL_ALPHA_SCALE	   params returns one value, the alpha scale factor
               used	during pixel transfers.	 See glPixelTransfer.

  GL_ALPHA_TEST		   params returns a single Boolean value indicating
               whether alpha testing of fragments is enabled.
               See glAlphaFunc.

  GL_ALPHA_TEST_FUNC	   params returns one value, the symbolic name of the
               alpha test function.	 See glAlphaFunc.

  GL_ALPHA_TEST_REF	   params returns one value, the reference value for
               the alpha test.  See	glAlphaFunc.  An integer
               value, if requested,	is linearly mapped from	the
               internal floating-point representation such that
               1.0 returns the most	positive representable
               integer value, and -1.0 returns the most negative
               representable integer value.

  GL_ATTRIB_STACK_DEPTH	   params returns one value, the depth of the
               attribute stack.  If	the stack is empty, zero is
               returned.  See glPushAttrib.

  GL_AUTO_NORMAL	   params returns a single Boolean value indicating
               whether 2-D map evaluation automatically generates
               surface normals.  See glMap2.

  GL_AUX_BUFFERS	   params returns one value, the number	of auxiliary
               color buffers.

  GL_BLEND		   params returns a single Boolean value indicating
               whether blending is enabled.	 See glBlendFunc.

  GL_BLEND_DST		   params returns one value, the symbolic constant
               identifying the destination blend function.	See
               glBlendFunc.

  GL_BLEND_SRC		   params returns one value, the symbolic constant
               identifying the source blend	function.  See
               glBlendFunc.

  GL_BLUE_BIAS		   params returns one value, the blue bias factor
               used	during pixel transfers.	 See glPixelTransfer.

  GL_BLUE_BITS		   params returns one value, the number	of blue
               bitplanes in	each color buffer.

  GL_BLUE_SCALE		   params returns one value, the blue scale factor
               used	during pixel transfers.	 See glPixelTransfer.

  GL_CLIP_PLANEi	   params returns a single Boolean value indicating
               whether the specified clipping plane	is enabled.
               See glClipPlane.

  GL_COLOR_ARRAY_EXT	   params returns a single boolean value, indicating
               whether the color array is enabled.	See
               glColorPointerEXT.

  GL_COLOR_ARRAY_COUNT_EXT params returns one value, the number	of colors in
               the color array, counting from the first, that are
               static.  See	glColorPointerEXT.

  GL_COLOR_ARRAY_SIZE_EXT  params returns one value, the number	of components
               per color in	the color array.  See
               glColorPointerEXT.

  GL_COLOR_ARRAY_STRIDE_EXT
               params returns one value, the byte offset between
               consecutive colors in the color array.  See
               glColorPointerEXT.

  GL_COLOR_ARRAY_TYPE_EXT  params returns one value, the data type of each
               component in	the color array.  See
               glColorPointerEXT.

  GL_COLOR_CLEAR_VALUE	   params returns four values: the red,	green, blue,
               and alpha values used to clear the color buffers.
               Integer values, if requested, are linearly mapped
               from	the internal floating-point representation
               such	that 1.0 returns the most positive
               representable integer value,	and -1.0 returns the
               most	negative representable integer value.  See
               glClearColor.

  GL_COLOR_MATERIAL	   params returns a single Boolean value indicating
               whether one or more material	parameters are
               tracking the	current	color.	See glColorMaterial.

  GL_COLOR_MATERIAL_FACE   params returns one value, a symbolic	constant
               indicating which materials have a parameter that
               is tracking the current color.  See
               glColorMaterial.

  GL_COLOR_MATERIAL_PARAMETER
               params returns one value, a symbolic	constant
               indicating which material parameters	are tracking
               the current color.  See glColorMaterial.

  GL_COLOR_WRITEMASK	   params returns four Boolean values: the red,
               green, blue,	and alpha write	enables	for the	color
               buffers.  See glColorMask.

  GL_CULL_FACE		   params returns a single Boolean value indicating
               whether polygon culling is enabled.	See
               glCullFace.

  GL_CULL_FACE_MODE	   params returns one value, a symbolic	constant
               indicating which polygon faces are to be culled.
               See glCullFace.

  GL_CURRENT_COLOR	   params returns four values: the red,	green, blue,
               and alpha values of the current color.  Integer
               values, if requested, are linearly mapped from the
               internal floating-point representation such that
               1.0 returns the most	positive representable
               integer value, and -1.0 returns the most negative
               representable integer value.	 See glColor.

  GL_CURRENT_INDEX	   params returns one value, the current color index.
               See glIndex.

  GL_CURRENT_NORMAL	   params returns three	values:	the x, y, and z
               values of the current normal.  Integer values, if
               requested, are linearly mapped from the internal
               floating-point representation such that 1.0
               returns the most positive representable integer
               value, and -1.0 returns the most negative
               representable integer value.	 See glNormal.

  GL_CURRENT_RASTER_COLOR  params returns four values: the red,	green, blue,
               and alpha values of the current raster position.
               Integer values, if requested, are linearly mapped
               from	the internal floating-point representation
               such	that 1.0 returns the most positive
               representable integer value,	and -1.0 returns the
               most	negative representable integer value.  See
               glRasterPos.

  GL_CURRENT_RASTER_DISTANCE
               params returns one value, the distance from the
               eye to the current raster position.	See
               glRasterPos.

  GL_CURRENT_RASTER_INDEX  params returns one value, the color index of	the
               current raster position.  See glRasterPos.

  GL_CURRENT_RASTER_POSITION
               params returns four values: the x, y, z, and	w
               components of the current raster position.  x, y,
               and z are in	window coordinates, and	w is in	clip
               coordinates.	 See glRasterPos.

  GL_CURRENT_RASTER_TEXTURE_COORDS
               params returns four values: the s, t, r, and	q
               current raster texture coordinates.	See
               glRasterPos and glTexCoord.

  GL_CURRENT_RASTER_POSITION_VALID
               params returns a single Boolean value indicating
               whether the current raster position is valid.  See
               glRasterPos.

  GL_CURRENT_TEXTURE_COORDS
               params returns four values: the s, t, r, and	q
               current texture coordinates.	 See glTexCoord.

  GL_DEPTH_BIAS		   params returns one value, the depth bias factor
               used	during pixel transfers.	 See glPixelTransfer.

  GL_DEPTH_BITS		   params returns one value, the number	of bitplanes
               in the depth	buffer.

  GL_DEPTH_CLEAR_VALUE	   params returns one value, the value that is used
               to clear the	depth buffer.  Integer values, if
               requested, are linearly mapped from the internal
               floating-point representation such that 1.0
               returns the most positive representable integer
               value, and -1.0 returns the most negative
               representable integer value.	 See glClearDepth.

  GL_DEPTH_FUNC		   params returns one value, the symbolic constant
               that	indicates the depth comparison function.  See
               glDepthFunc.

  GL_DEPTH_RANGE	   params returns two values: the near and far
               mapping limits for the depth	buffer.	 Integer
               values, if requested, are linearly mapped from the
               internal floating-point representation such that
               1.0 returns the most	positive representable
               integer value, and -1.0 returns the most negative
               representable integer value.	 See glDepthRange.

  GL_DEPTH_SCALE	   params returns one value, the depth scale factor
               used	during pixel transfers.	 See glPixelTransfer.

  GL_DEPTH_TEST		   params returns a single Boolean value indicating
               whether depth testing of fragments is enabled.
               See glDepthFunc and glDepthRange.

  GL_DEPTH_WRITEMASK	   params returns a single Boolean value indicating
               if the depth	buffer is enabled for writing.	See
               glDepthMask.

  GL_DITHER		   params returns a single Boolean value indicating
               whether dithering of	fragment colors	and indices
               is enabled.

  GL_DOUBLEBUFFER	   params returns a single Boolean value indicating
               whether double buffering is supported.

  GL_DRAW_BUFFER	   params returns one value, a symbolic	constant
               indicating which buffers are	being drawn to.	 See
               glDrawBuffer.

  GL_EDGE_FLAG		   params returns a single Boolean value indication
               whether the current edge flag is true or false.
               See glEdgeFlag.

  GL_EDGE_FLAG_ARRAY_EXT   params returns a single boolean value, indicating
               whether the edge flag array is enabled.  See
               glEdgeFlagPointerEXT.

  GL_EDGE_FLAG_ARRAY_COUNT_EXT
               params returns one value, the number	of edge	flags
               in the edge flag array, counting from the first,
               that	are static.  See glEdgeFlagPointerEXT.

  GL_EDGE_FLAG_ARRAY_STRIDE_EXT
               params returns one value, the byte offset between
               consecutive edge flags in the edge flag array.
               See glEdgeFlagPointerEXT.

  GL_FOG		   params returns a single Boolean value indicating
               whether fogging is enabled.	See glFog.

  GL_FOG_COLOR		   params returns four values: the red,	green, blue,
               and alpha components	of the fog color.  Integer
               values, if requested, are linearly mapped from the
               internal floating-point representation such that
               1.0 returns the most	positive representable
               integer value, and -1.0 returns the most negative
               representable integer value.	 See glFog.

  GL_FOG_DENSITY	   params returns one value, the fog density
               parameter.  See glFog.

  GL_FOG_END		   params returns one value, the end factor for	the
               linear fog equation.	 See glFog.

  GL_FOG_HINT		   params returns one value, a symbolic	constant
               indicating the mode of the fog hint.	 See glHint.

  GL_FOG_INDEX		   params returns one value, the fog color index.
               See glFog.

  GL_FOG_MODE		   params returns one value, a symbolic	constant
               indicating which fog	equation is selected.  See
               glFog.

  GL_FOG_START		   params returns one value, the start factor for the
               linear fog equation.	 See glFog.

  GL_FRONT_FACE		   params returns one value, a symbolic	constant
               indicating whether clockwise	or counterclockwise
               polygon winding is treated as front-facing.	See
               glFrontFace.

  GL_GREEN_BIAS		   params returns one value, the green bias factor
               used	during pixel transfers.

  GL_GREEN_BITS		   params returns one value, the number	of green
               bitplanes in	each color buffer.

  GL_GREEN_SCALE	   params returns one value, the green scale factor
               used	during pixel transfers.	 See glPixelTransfer.

  GL_INDEX_ARRAY_EXT	   params returns a single boolean value, indicating
               whether the color index array is enabled.  See
               glIndexPointerEXT.

  GL_INDEX_ARRAY_COUNT_EXT params returns one value, the number	of color
               indexes in the color	index array, counting from
               the first, that are static.	See
               glIndexPointerEXT.

  GL_INDEX_ARRAY_STRIDE_EXT
               params returns one value, the byte offset between
               consecutive color indexes in	the color index
               array.  See glIndexPointerEXT.

  GL_INDEX_ARRAY_TYPE_EXT  params returns one value, the data type of indexes
               in the color	index array.  See glIndexPointerEXT.

  GL_INDEX_BITS		   params returns one value, the number	of bitplanes
               in each color index buffer.

  GL_INDEX_CLEAR_VALUE	   params returns one value, the color index used to
               clear the color index buffers.  See glClearIndex.

  GL_INDEX_MODE		   params returns a single Boolean value indicating
               whether the GL is in	color index mode (true)	or
               RGBA	mode (false).

  GL_INDEX_OFFSET	   params returns one value, the offset	added to
               color and stencil indices during pixel transfers.
               See glPixelTransfer.

  GL_INDEX_SHIFT	   params returns one value, the amount	that color
               and stencil indices are shifted during pixel
               transfers.  See glPixelTransfer.

  GL_INDEX_WRITEMASK	   params returns one value, a mask indicating which
               bitplanes of	each color index buffer	can be
               written.  See glIndexMask.

  GL_LIGHTi		   params returns a single Boolean value indicating
               whether the specified light is enabled.  See
               glLight and glLightModel.

  GL_LIGHTING		   params returns a single Boolean value indicating
               whether lighting is enabled.	 See glLightModel.

  GL_LIGHT_MODEL_AMBIENT   params returns four values: the red,	green, blue,
               and alpha components	of the ambient intensity of
               the entire scene.  Integer values, if requested,
               are linearly	mapped from the	internal floating-
               point representation	such that 1.0 returns the
               most	positive representable integer value, and
               -1.0	returns	the most negative representable
               integer value.  See glLightModel.

  GL_LIGHT_MODEL_LOCAL_VIEWER
               params returns a single Boolean value indicating
               whether specular reflection calculations treat the
               viewer as being local to the	scene.	See
               glLightModel.

  GL_LIGHT_MODEL_TWO_SIDE  params returns a single Boolean value indicating
               whether separate materials are used to compute
               lighting for	front- and back-facing polygons.  See
               glLightModel.

  GL_LINE_SMOOTH	   params returns a single Boolean value indicating
               whether antialiasing	of lines is enabled.  See
               glLineWidth.

  GL_LINE_SMOOTH_HINT	   params returns one value, a symbolic	constant
               indicating the mode of the line antialiasing	hint.
               See glHint.

  GL_LINE_STIPPLE	   params returns a single Boolean value indicating
               whether stippling of	lines is enabled.  See
               glLineStipple.

  GL_LINE_STIPPLE_PATTERN  params returns one value, the 16-bit	line stipple
               pattern.  See glLineStipple.

  GL_LINE_STIPPLE_REPEAT   params returns one value, the line stipple repeat
               factor.  See	glLineStipple.

  GL_LINE_WIDTH		   params returns one value, the line width as
               specified with glLineWidth.

  GL_LINE_WIDTH_GRANULARITY
               params returns one value, the width difference
               between adjacent supported widths for antialiased
               lines.  See glLineWidth.

  GL_LINE_WIDTH_RANGE	   params returns two values: the smallest and
               largest supported widths for	antialiased lines.
               See glLineWidth.

  GL_LIST_BASE		   params returns one value, the base offset added to
               all names in	arrays presented to glCallLists.  See
               glListBase.

  GL_LIST_INDEX		   params returns one value, the name of the display
               list	currently under	construction.  Zero is
               returned if no display list is currently under
               construction.  See glNewList.

  GL_LIST_MODE		   params returns one value, a symbolic	constant
               indicating the construction mode of the display
               list	currently being	constructed.  See glNewList.

  GL_LOGIC_OP		   params returns a single Boolean value indicating
               whether fragment indexes are	merged into the
               framebuffer using a logical operation.  See
               glLogicOp.

  GL_LOGIC_OP_MODE	   params returns one value, a symbolic	constant
               indicating the selected logic operational mode.
               See glLogicOp.

  GL_MAP1_COLOR_4	   params returns a single Boolean value indicating
               whether 1D evaluation generates colors.  See
               glMap1.

  GL_MAP1_GRID_DOMAIN	   params returns two values: the endpoints of the
               1-D map's grid domain.  See glMapGrid.

  GL_MAP1_GRID_SEGMENTS	   params returns one value, the number	of partitions
               in the 1-D map's grid domain.  See glMapGrid.

  GL_MAP1_INDEX		   params returns a single Boolean value indicating
               whether 1D evaluation generates color indices.
               See glMap1.

  GL_MAP1_NORMAL	   params returns a single Boolean value indicating
               whether 1D evaluation generates normals.  See
               glMap1.

  GL_MAP1_TEXTURE_COORD_1  params returns a single Boolean value indicating
               whether 1D evaluation generates 1D texture
               coordinates.	 See glMap1.

  GL_MAP1_TEXTURE_COORD_2  params returns a single Boolean value indicating
               whether 1D evaluation generates 2D texture
               coordinates.	 See glMap1.

  GL_MAP1_TEXTURE_COORD_3  params returns a single Boolean value indicating
               whether 1D evaluation generates 3D texture
               coordinates.	 See glMap1.

  GL_MAP1_TEXTURE_COORD_4  params returns a single Boolean value indicating
               whether 1D evaluation generates 4D texture
               coordinates.	 See glMap1.

  GL_MAP1_VERTEX_3	   params returns a single Boolean value indicating
               whether 1D evaluation generates 3D vertex
               coordinates.	 See glMap1.

  GL_MAP1_VERTEX_4	   params returns a single Boolean value indicating
               whether 1D evaluation generates 4D vertex
               coordinates.	 See glMap1.

  GL_MAP2_COLOR_4	   params returns a single Boolean value indicating
               whether 2D evaluation generates colors.  See
               glMap2.

  GL_MAP2_GRID_DOMAIN	   params returns four values: the endpoints of	the
               2-D map's i and j grid domains.  See	glMapGrid.

  GL_MAP2_GRID_SEGMENTS	   params returns two values: the number of
               partitions in the 2-D map's i and j grid domains.
               See glMapGrid.

  GL_MAP2_INDEX		   params returns a single Boolean value indicating
               whether 2D evaluation generates color indices.
               See glMap2.

  GL_MAP2_NORMAL	   params returns a single Boolean value indicating
               whether 2D evaluation generates normals.  See
               glMap2.

  GL_MAP2_TEXTURE_COORD_1  params returns a single Boolean value indicating
               whether 2D evaluation generates 1D texture
               coordinates.	 See glMap2.

  GL_MAP2_TEXTURE_COORD_2  params returns a single Boolean value indicating
               whether 2D evaluation generates 2D texture
               coordinates.	 See glMap2.

  GL_MAP2_TEXTURE_COORD_3  params returns a single Boolean value indicating
               whether 2D evaluation generates 3D texture
               coordinates.	 See glMap2.

  GL_MAP2_TEXTURE_COORD_4  params returns a single Boolean value indicating
               whether 2D evaluation generates 4D texture
               coordinates.	 See glMap2.

  GL_MAP2_VERTEX_3	   params returns a single Boolean value indicating
               whether 2D evaluation generates 3D vertex
               coordinates.	 See glMap2.

  GL_MAP2_VERTEX_4	   params returns a single Boolean value indicating
               whether 2D evaluation generates 4D vertex
               coordinates.	 See glMap2.

  GL_MAP_COLOR		   params returns a single Boolean value indicating
               if colors and color indices are to be replaced by
               table lookup	during pixel transfers.	 See
               glPixelTransfer.

  GL_MAP_STENCIL	   params returns a single Boolean value indicating
               if stencil indices are to be	replaced by table
               lookup during pixel transfers.  See
               glPixelTransfer.

  GL_MATRIX_MODE	   params returns one value, a symbolic	constant
               indicating which matrix stack is currently the
               target of all matrix	operations.  See
               glMatrixMode.

  GL_MAX_ATTRIB_STACK_DEPTH
               params returns one value, the maximum supported
               depth of the	attribute stack.  See glPushAttrib.

  GL_MAX_CLIP_PLANES	   params returns one value, the maximum number	of
               application-defined clipping	planes.	 See
               glClipPlane.

  GL_MAX_EVAL_ORDER	   params returns one value, the maximum equation
               order supported by 1-D and 2-D evaluators.  See
               glMap1 and glMap2.

  GL_MAX_LIGHTS		   params returns one value, the maximum number	of
               lights.  See	glLight.

  GL_MAX_LIST_NESTING	   params returns one value, the maximum recursion
               depth allowed during	display-list traversal.	 See
               glCallList.

  GL_MAX_MODELVIEW_STACK_DEPTH
               params returns one value, the maximum supported
               depth of the	modelview matrix stack.	 See
               glPushMatrix.

  GL_MAX_NAME_STACK_DEPTH  params returns one value, the maximum supported
               depth of the	selection name stack.  See
               glPushName.

  GL_MAX_PIXEL_MAP_TABLE   params returns one value, the maximum supported
               size	of a glPixelMap	lookup table.  See
               glPixelMap.

  GL_MAX_PROJECTION_STACK_DEPTH
               params returns one value, the maximum supported
               depth of the	projection matrix stack.  See
               glPushMatrix.

  GL_MAX_TEXTURE_SIZE	   params returns one value, the maximum width or
               height of any texture image (without	borders).
               See glTexImage1D and	glTexImage2D.

  GL_MAX_TEXTURE_STACK_DEPTH
               params returns one value, the maximum supported
               depth of the	texture	matrix stack.  See
               glPushMatrix.

  GL_MAX_VIEWPORT_DIMS	   params returns two values: the maximum supported
               width and height of the viewport.  See glViewport.

  GL_MODELVIEW_MATRIX	   params returns sixteen values: the modelview
               matrix on the top of	the modelview matrix stack.
               See glPushMatrix.

  GL_MODELVIEW_STACK_DEPTH params returns one value, the number	of matrices
               on the modelview matrix stack.  See glPushMatrix.

  GL_NAME_STACK_DEPTH	   params returns one value, the number	of names on
               the selection name stack.  See glPushMatrix.

  GL_NORMAL_ARRAY_EXT	   params returns a single boolean value, indicating
               whether the normal array is enabled.	 See
               glNormalPointerEXT.

  GL_NORMAL_ARRAY_COUNT_EXT
               params returns one value, the number	of normals in
               the normal array, counting from the first, that
               are static.	See glNormalPointerEXT.

  GL_NORMAL_ARRAY_STRIDE_EXT
               params returns one value, the byte offset between
               consecutive normals in the normal array.  See
               glNormalPointerEXT.

  GL_NORMAL_ARRAY_TYPE_EXT params returns one value, the data type of each
               coordinate in the normal array.  See
               glNormalPointerEXT.

  GL_NORMALIZE		   params returns a single Boolean value indicating
               whether normals are automatically scaled to unit
               length after	they have been transformed to eye
               coordinates.	 See glNormal.

  GL_PACK_ALIGNMENT	   params returns one value, the byte alignment	used
               for writing pixel data to memory.  See
               glPixelStore.

  GL_PACK_LSB_FIRST	   params returns a single Boolean value indicating
               whether single-bit pixels being written to memory
               are written first to	the least significant bit of
               each	unsigned byte.	See glPixelStore.

  GL_PACK_ROW_LENGTH	   params returns one value, the row length used for
               writing pixel data to memory.  See glPixelStore.

  GL_PACK_SKIP_PIXELS	   params returns one value, the number	of pixel
               locations skipped before the	first pixel is
               written into	memory.	 See glPixelStore.

  GL_PACK_SKIP_ROWS	   params returns one value, the number	of rows	of
               pixel locations skipped before the first pixel is
               written into	memory.	 See glPixelStore.

  GL_PACK_SWAP_BYTES	   params returns a single Boolean value indicating
               whether the bytes of	two-byte and four-byte pixel
               indices and components are swapped before being
               written to memory.  See glPixelStore.

  GL_PERSPECTIVE_CORRECTION_HINT
               params returns one value, a symbolic	constant
               indicating the mode of the perspective correction
               hint.  See glHint.

  GL_PIXEL_MAP_A_TO_A_SIZE params returns one value, the size of the alpha-
               to-alpha pixel translation table.  See glPixelMap.

  GL_PIXEL_MAP_B_TO_B_SIZE params returns one value, the size of the blue-
               to-blue pixel translation table.  See glPixelMap.

  GL_PIXEL_MAP_G_TO_G_SIZE params returns one value, the size of the green-
               to-green pixel translation table.  See glPixelMap.

  GL_PIXEL_MAP_I_TO_A_SIZE params returns one value, the size of the index-
               to-alpha pixel translation table.  See glPixelMap.

  GL_PIXEL_MAP_I_TO_B_SIZE params returns one value, the size of the index-
               to-blue pixel translation table.  See glPixelMap.

  GL_PIXEL_MAP_I_TO_G_SIZE params returns one value, the size of the index-
               to-green pixel translation table.  See glPixelMap.

  GL_PIXEL_MAP_I_TO_I_SIZE params returns one value, the size of the index-
               to-index pixel translation table.  See glPixelMap.

  GL_PIXEL_MAP_I_TO_R_SIZE params returns one value, the size of the index-
               to-red pixel	translation table.  See	glPixelMap.

  GL_PIXEL_MAP_R_TO_R_SIZE params returns one value, the size of the red-to-
               red pixel translation table.	 See glPixelMap.

  GL_PIXEL_MAP_S_TO_S_SIZE params returns one value, the size of the
               stencil-to-stencil pixel translation	table.	See
               glPixelMap.

  GL_POINT_SIZE		   params returns one value, the point size as
               specified by	glPointSize.

  GL_POINT_SIZE_GRANULARITY
               params returns one value, the size difference
               between adjacent supported sizes for	antialiased
               points.  See	glPointSize.

  GL_POINT_SIZE_RANGE	   params returns two values: the smallest and
               largest supported sizes for antialiased points.
               See glPointSize.

  GL_POINT_SMOOTH	   params returns a single Boolean value indicating
               whether antialiasing	of points is enabled.  See
               glPointSize.

  GL_POINT_SMOOTH_HINT	   params returns one value, a symbolic	constant
               indicating the mode of the point antialiasing
               hint.  See glHint.

  GL_POLYGON_MODE	   params returns two values: symbolic constants
               indicating whether front-facing and back-facing
               polygons are	rasterized as points, lines, or
               filled polygons.  See glPolygonMode.

  GL_POLYGON_SMOOTH	   params returns a single Boolean value indicating
               whether antialiasing	of polygons is enabled.	 See
               glPolygonMode.

  GL_POLYGON_SMOOTH_HINT   params returns one value, a symbolic	constant
               indicating the mode of the polygon antialiasing
               hint.  See glHint.

  GL_POLYGON_STIPPLE	   params returns a single Boolean value indicating
               whether stippling of	polygons is enabled.  See
               glPolygonStipple.

  GL_PROJECTION_MATRIX	   params returns sixteen values: the projection
               matrix on the top of	the projection matrix stack.
               See glPushMatrix.

  GL_PROJECTION_STACK_DEPTH
               params returns one value, the number	of matrices
               on the projection matrix stack.  See	glPushMatrix.

  GL_READ_BUFFER	   params returns one value, a symbolic	constant
               indicating which color buffer is selected for
               reading.
               See glReadPixels and	glAccum.

  GL_RED_BIAS		   params returns one value, the red bias factor used
               during pixel	transfers.

  GL_RED_BITS		   params returns one value, the number	of red
               bitplanes in	each color buffer.

  GL_RED_SCALE		   params returns one value, the red scale factor
               used	during pixel transfers.	 See glPixelTransfer.

  GL_RENDER_MODE	   params returns one value, a symbolic	constant
               indicating whether the GL is	in render, select, or
               feedback mode.  See glRenderMode.

  GL_RGBA_MODE		   params returns a single Boolean value indicating
               whether the GL is in	RGBA mode (true) or color
               index mode (false).	See glColor.

  GL_SCISSOR_BOX	   params returns four values: the x and y window
               coordinates of the scissor box, followed by its
               width and height.  See glScissor.

  GL_SCISSOR_TEST	   params returns a single Boolean value indicating
               whether scissoring is enabled.  See glScissor.

  GL_SHADE_MODEL	   params returns one value, a symbolic	constant
               indicating whether the shading mode is flat or
               smooth.  See	glShadeModel.

  GL_STENCIL_BITS	   params returns one value, the number	of bitplanes
               in the stencil buffer.

  GL_STENCIL_CLEAR_VALUE   params returns one value, the index to which	the
               stencil bitplanes are cleared.  See
               glClearStencil.

  GL_STENCIL_FAIL	   params returns one value, a symbolic	constant
               indicating what action is taken when	the stencil
               test	fails.	See glStencilOp.

  GL_STENCIL_FUNC	   params returns one value, a symbolic	constant
               indicating what function is used to compare the
               stencil reference value with	the stencil buffer
               value.  See glStencilFunc.

  GL_STENCIL_PASS_DEPTH_FAIL
               params returns one value, a symbolic	constant
               indicating what action is taken when	the stencil
               test	passes,	but the	depth test fails.  See
               glStencilOp.

  GL_STENCIL_PASS_DEPTH_PASS
               params returns one value, a symbolic	constant
               indicating what action is taken when	the stencil
               test	passes and the depth test passes.  See
               glStencilOp.

  GL_STENCIL_REF	   params returns one value, the reference value that
               is compared with the	contents of the	stencil
               buffer.  See	glStencilFunc.

  GL_STENCIL_TEST	   params returns a single Boolean value indicating
               whether stencil testing of fragments	is enabled.
               See glStencilFunc and glStencilOp.

  GL_STENCIL_VALUE_MASK	   params returns one value, the mask that is used to
               mask	both the stencil reference value and the
               stencil buffer value	before they are	compared.
               See glStencilFunc.

  GL_STENCIL_WRITEMASK	   params returns one value, the mask that controls
               writing of the stencil bitplanes.  glStencilMask.

  GL_STEREO		   params returns a single Boolean value indicating
               whether stereo buffers (left	and right) are
               supported.

  GL_SUBPIXEL_BITS	   params returns one value, an	estimate of the
               number of bits of subpixel resolution that are
               used	to position rasterized geometry	in window
               coordinates.

  GL_TEXTURE_1D		   params returns a single Boolean value indicating
               whether 1D texture mapping is enabled.  See
               glTexImage1D.

  GL_TEXTURE_2D		   params returns a single Boolean value indicating
               whether 2D texture mapping is enabled.  See
               glTexImage2D.

  GL_TEXTURE_COORD_ARRAY_EXT
               params returns a single boolean value, indicating
               whether the texture coordinate array	is enabled.
               See glTexCoordPointerEXT.

  GL_TEXTURE_COORD_ARRAY_COUNT_EXT
               params returns one value, the number	of elements
               in the texture coordinate array, counting from the
               first, that are static.  See	glTexCoordPointerEXT.

  GL_TEXTURE_COORD_ARRAY_SIZE_EXT
               params returns one value, the number	of
               coordinates per element in the texture coordinate
               array.  See glTexCoordPointerEXT.

  GL_TEXTURE_COORD_ARRAY_STRIDE_EXT
               params returns one value, the byte offset between
               consecutive elements	in the texture coordinate
               array.  See glTexCoordPointerEXT.

  GL_TEXTURE_COORD_ARRAY_TYPE_EXT
               params returns one value, the data type of the
               coordinates in the texture coordinate array.	 See
               glTexCoordPointerEXT.

  GL_TEXTURE_GEN_S	   params returns a single Boolean value indicating
               whether automatic generation	of the S texture
               coordinate is enabled.  See glTexGen.

  GL_TEXTURE_GEN_T	   params returns a single Boolean value indicating
               whether automatic generation	of the T texture
               coordinate is enabled.  See glTexGen.

  GL_TEXTURE_GEN_R	   params returns a single Boolean value indicating
               whether automatic generation	of the R texture
               coordinate is enabled.  See glTexGen.

  GL_TEXTURE_GEN_Q	   params returns a single Boolean value indicating
               whether automatic generation	of the Q texture
               coordinate is enabled.  See glTexGen.

  GL_TEXTURE_MATRIX	   params returns sixteen values: the texture matrix
               on the top of the texture matrix stack.  See
               glPushMatrix.

  GL_TEXTURE_STACK_DEPTH   params returns one value, the number	of matrices
               on the texture matrix stack.	 See glPushMatrix.

  GL_UNPACK_ALIGNMENT	   params returns one value, the byte alignment	used
               for reading pixel data from memory.	See
               glPixelStore.

  GL_UNPACK_LSB_FIRST	   params returns a single Boolean value indicating
               whether single-bit pixels being read	from memory
               are read first from the least significant bit of
               each	unsigned byte.	See glPixelStore.

  GL_UNPACK_ROW_LENGTH	   params returns one value, the row length used for
               reading pixel data from memory.  See	glPixelStore.

  GL_UNPACK_SKIP_PIXELS	   params returns one value, the number	of pixel
               locations skipped before the	first pixel is read
               from	memory.	 See glPixelStore.

  GL_UNPACK_SKIP_ROWS	   params returns one value, the number	of rows	of
               pixel locations skipped before the first pixel is
               read	from memory.  See glPixelStore.

  GL_UNPACK_SWAP_BYTES	   params returns a single Boolean value indicating
               whether the bytes of	two-byte and four-byte pixel
               indices and components are swapped after being
               read	from memory.  See glPixelStore.

  GL_VERTEX_ARRAY_EXT	   params returns a single boolean value, indicating
               whether the vertex array is enabled.	 See
               glVertexPointerEXT.

  GL_VERTEX_ARRAY_COUNT_EXT
               params returns one value, the number	of vertexes
               in the vertex array,	counting from the first, that
               are static.	See glVertexPointerEXT.

  GL_VERTEX_ARRAY_SIZE_EXT params returns one value, the number	of
               coordinates per vertex in the vertex	array.	See
               glVertexPointerEXT.

  GL_VERTEX_ARRAY_STRIDE_EXT
               params returns one value, the byte offset between
               consecutive vertexes	in the vertex array.  See
               glVertexPointerEXT.

  GL_VERTEX_ARRAY_TYPE_EXT params returns one value, the data type of each
               coordinate in the vertex array.  See
               glVertexPointerEXT.

  GL_VIEWPORT		   params returns four values: the x and y window
               coordinates of the viewport,	followed by its	width
               and height.	See glViewport.

  GL_ZOOM_X		   params returns one value, the x pixel zoom factor.
               See glPixelZoom.

  GL_ZOOM_Y		   params returns one value, the y pixel zoom factor.
               See glPixelZoom.

  Many of the Boolean parameters can also be queried more easily using
  glIsEnabled.

Errors

  GL_INVALID_ENUM is generated if pname	is not an accepted value.

  GL_INVALID_OPERATION is generated if glGet is	executed between the
  execution of glBegin and the corresponding execution of glEnd.

See Also

  glGetClipPlane, glGetError, glGetLight, glGetMap, glGetMaterial,
  glGetPixelMap, glGetPolygonStipple, glGetString, glGetTexEnv,	glGetTexGen,
  glGetTexImage, glGetTexLevelParameter, glGetTexParameter, glIsEnabled

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glGetClipPlane.3gl

Name

  glGetClipPlane - return the coefficients of the specified clipping plane

C Specification

  void glGetClipPlane( GLenum plane,
               GLdouble	*equation )

Parameters

  plane	    Specifies a	clipping plane.	 The number of clipping	planes
        depends on the implementation, but at least	six clipping planes
        are	supported.  They are identified	by symbolic names of the form
        GL_CLIP_PLANEi where 0 <= i < GL_MAX_CLIP_PLANES.

  equation  Returns four double-precision values that are the coefficients of
        the	plane equation of plane	in eye coordinates.

Description

  glGetClipPlane returns in equation the four coefficients of the plane
  equation for plane.

Notes

  It is	always the case	that GL_CLIP_PLANEi = GL_CLIP_PLANE0 + i.

  If an	error is generated, no change is made to the contents of equation.

Errors

  GL_INVALID_ENUM is generated if plane	is not an accepted value.

  GL_INVALID_OPERATION is generated if glGetClipPlane is executed between the
  execution of glBegin and the corresponding execution of glEnd.

See Also

  glClipPlane

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glGetError.3gl

Name

  glGetError - return error information

C Specification

  GLenum glGetError( void )

Description

  glGetError returns the value of the error flag.  Each	detectable error is
  assigned a numeric code and symbolic name.  When an error occurs, the	error
  flag is set to the appropriate error code value.  No other errors are
  recorded until glGetError is called, the error code is returned, and the
  flag is reset	to GL_NO_ERROR.	 If a call to glGetError returns GL_NO_ERROR,
  there	has been no detectable error since the last call to glGetError,	or
  since	the GL was initialized.

  To allow for distributed implementations, there may be several error flags.
  If any single	error flag has recorded	an error, the value of that flag is
  returned and that flag is reset to GL_NO_ERROR when glGetError is called.
  If more than one flag	has recorded an	error, glGetError returns and clears
  an arbitrary error flag value.  Thus,	glGetError should always be called in
  a loop, until	it returns GL_NO_ERROR,	if all error flags are to be reset.

  Initially, all error flags are set to	GL_NO_ERROR.

  The currently	defined	errors are as follows:

  GL_NO_ERROR			No error has been recorded.  The value of
                this symbolic constant is guaranteed to	be
                zero.

  GL_INVALID_ENUM		An unacceptable	value is specified for an
                enumerated argument.  The offending command
                is ignored, having no side effect other	than
                to set the error flag.

  GL_INVALID_VALUE		A numeric argument is out of range.  The
                offending command is ignored, having no	side
                effect other than to set the error flag.

  GL_INVALID_OPERATION		The specified operation	is not allowed in the
                current	state.	The offending command is
                ignored, having	no side	effect other than to
                set the	error flag.

  GL_STACK_OVERFLOW		This command would cause a stack overflow.
                The offending command is ignored, having no
                side effect other than to set the error	flag.

  GL_STACK_UNDERFLOW		This command would cause a stack underflow.
                The offending command is ignored, having no
                side effect other than to set the error	flag.

  GL_OUT_OF_MEMORY		There is not enough memory left	to execute
                the command.  The state	of the GL is
                undefined, except for the state	of the error
                flags, after this error	is recorded.

  When an error	flag is	set, results of	a GL operation are undefined only if
  GL_OUT_OF_MEMORY has occurred.  In all other cases, the command generating
  the error is ignored and has no effect on the	GL state or frame buffer
  contents.  If	the generating command returns a value,	it returns zero. If
  glGetError itself generates an error it returns zero.

Errors

  GL_INVALID_OPERATION is generated if glGetError is executed between the
  execution of glBegin and the corresponding execution of glEnd.  In this
  case glGetError returns zero.

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glGetLight.3gl

Name

  glGetLightfv,	glGetLightiv - return light source parameter values

C Specification

  void glGetLightfv( GLenum light,
             GLenum pname,
             GLfloat *params )
  void glGetLightiv( GLenum light,
             GLenum pname,
             GLint *params )

Parameters

  light	  Specifies a light source.  The number	of possible lights depends on
      the implementation, but at least eight lights	are supported.	They
      are identified by symbolic names of the form GL_LIGHTi where 0 <= i
      < GL_MAX_LIGHTS.

  pname	  Specifies a light source parameter for light.	 Accepted symbolic
      names	are GL_AMBIENT,	GL_DIFFUSE, GL_SPECULAR, GL_POSITION,
      GL_SPOT_DIRECTION, GL_SPOT_EXPONENT, GL_SPOT_CUTOFF,
      GL_CONSTANT_ATTENUATION, GL_LINEAR_ATTENUATION , and
      GL_QUADRATIC_ATTENUATION.

  params  Returns the requested	data.

Description

  glGetLight returns in	params the value or values of a	light source
  parameter.  light names the light and	is a symbolic name of the form
  GL_LIGHTi for	0<=i<GL_MAX_LIGHTS, where GL_MAX_LIGHTS is an implementation
  dependent constant that is greater than or equal to eight.  pname specifies
  one of ten light source parameters, again by symbolic	name.

  The parameters are as	follows:

  GL_AMBIENT	      params returns four integer or floating-point values
              representing the ambient intensity of the	light source.
              Integer values, when requested, are linearly mapped
              from the internal	floating-point representation such
              that 1.0 maps to the most	positive representable
              integer value, and -1.0 maps to the most negative
              representable integer value.  If the internal value is
              outside the range	[-1,1],	the corresponding integer
              return value is undefined.

  GL_DIFFUSE	      params returns four integer or floating-point values
              representing the diffuse intensity of the	light source.
              Integer values, when requested, are linearly mapped
              from the internal	floating-point representation such
              that 1.0 maps to the most	positive representable
              integer value, and -1.0 maps to the most negative
              representable integer value.  If the internal value is
              outside the range	[-1,1],	the corresponding integer
              return value is undefined.

  GL_SPECULAR	      params returns four integer or floating-point values
              representing the specular	intensity of the light
              source.  Integer values, when requested, are linearly
              mapped from the internal floating-point representation
              such that	1.0 maps to the	most positive representable
              integer value, and -1.0 maps to the most negative
              representable integer value.  If the internal value is
              outside the range	[-1,1],	the corresponding integer
              return value is undefined.

  GL_POSITION	      params returns four integer or floating-point values
              representing the position	of the light source.  Integer
              values, when requested, are computed by rounding the
              internal floating-point values to	the nearest integer
              value.  The returned values are those maintained in eye
              coordinates.  They will not be equal to the values
              specified	using glLight, unless the modelview matrix
              was identity at the time glLight was called.

  GL_SPOT_DIRECTION   params returns three integer or floating-point values
              representing the direction of the	light source.
              Integer values, when requested, are computed by
              rounding the internal floating-point values to the
              nearest integer value.  The returned values are those
              maintained in eye	coordinates.  They will	not be equal
              to the values specified using glLight, unless the
              modelview	matrix was identity at the time	glLight	was
              called.  Although	spot direction is normalized before
              being used in the	lighting equation, the returned
              values are the transformed versions of the specified
              values prior to normalization.

  GL_SPOT_EXPONENT    params returns a single integer or floating-point	value
              representing the spot exponent of	the light.  An
              integer value, when requested, is	computed by rounding
              the internal floating-point representation to the
              nearest integer.

  GL_SPOT_CUTOFF      params returns a single integer or floating-point	value
              representing the spot cutoff angle of the	light.	An
              integer value, when requested, is	computed by rounding
              the internal floating-point representation to the
              nearest integer.

  GL_CONSTANT_ATTENUATION
              params returns a single integer or floating-point	value
              representing the constant	(not distance related)
              attenuation of the light.	 An integer value, when
              requested, is computed by	rounding the internal
              floating-point representation to the nearest integer.

  GL_LINEAR_ATTENUATION
              params returns a single integer or floating-point	value
              representing the linear attenuation of the light.	 An
              integer value, when requested, is	computed by rounding
              the internal floating-point representation to the
              nearest integer.

  GL_QUADRATIC_ATTENUATION
              params returns a single integer or floating-point	value
              representing the quadratic attenuation of	the light.
              An integer value,	when requested,	is computed by
              rounding the internal floating-point representation to
              the nearest integer.

Notes

  It is	always the case	that GL_LIGHTi = GL_LIGHT0 + i.

  If an	error is generated, no change is made to the contents of params.

Errors

  GL_INVALID_ENUM is generated if light	or pname is not	an accepted value.

  GL_INVALID_OPERATION is generated if glGetLight is called between a call to
  glBegin and the corresponding	call to	glEnd.

See Also

  glLight

Introduction | Alphabetic | Specification

Last Edited: Fri, May 26, 1995

AFV


glGetMap.3gl

Name

  glGetMapdv, glGetMapfv, glGetMapiv - return evaluator	parameters

C Specification

  void glGetMapdv( GLenum target,
           GLenum query,
           GLdouble *v )
  void glGetMapfv( GLenum target,
           GLenum query,
           GLfloat *v )
  void glGetMapiv( GLenum target,
           GLenum query,
           GLint *v )

Parameters

  target  Specifies the	symbolic name of a map.	 Accepted values are
      GL_MAP1_COLOR_4, GL_MAP1_INDEX, GL_MAP1_NORMAL,
      GL_MAP1_TEXTURE_COORD_1, GL_MAP1_TEXTURE_COORD_2,
      GL_MAP1_TEXTURE_COORD_3, GL_MAP1_TEXTURE_COORD_4, GL_MAP1_VERTEX_3,
      GL_MAP1_VERTEX_4, GL_MAP2_COLOR_4 , GL_MAP2_INDEX, GL_MAP2_NORMAL,
      GL_MAP2_TEXTURE_COORD_1, GL_MAP2_TEXTURE_COORD_2,
      GL_MAP2_TEXTURE_COORD_3, GL_MAP2_TEXTURE_COORD_4, GL_MAP2_VERTEX_3,
      and GL_MAP2_VERTEX_4.

  query	  Specifies which parameter to return.	Symbolic names GL_COEFF,
      GL_ORDER, and	GL_DOMAIN are accepted.

  v	  Returns the requested	data.

Description

  glMap1 and glMap2 define evaluators.	glGetMap returns evaluator
  parameters.  target chooses a	map, query selects a specific parameter, and
  v points to storage where the	values will be returned.

  The acceptable values	for the	target parameter are described in the glMap1
  and  glMap2 reference	pages.

  query	can assume the following values:

  GL_COEFF	    v returns the control points for the evaluator function.
            One-dimensional evaluators return order control points,
            and	two-dimensional	evaluators return uorderxvorder
            control points.  Each control point	consists of one, two,
            three, or four integer, single-precision floating-point,
            or double-precision	floating-point values, depending on
            the	type of	the evaluator.	Two-dimensional	control
            points are returned	in row-major order, incrementing the
            uorder index quickly, and the vorder index after each
            row.  Integer values, when requested, are computed by
            rounding the internal floating-point values	to the
            nearest integer values.

  GL_ORDER	    v returns the order	of the evaluator function.  One-
            dimensional	evaluators return a single value, order.
            Two-dimensional evaluators return two values, uorder and
            vorder.

  GL_DOMAIN	    v returns the linear u and v mapping parameters.  One-
            dimensional	evaluators return two values, u1 and u2, as
            specified by glMap1.  Two-dimensional evaluators return
            four values	(u1, u2, v1, and v2) as	specified by glMap2.
            Integer values, when requested, are	computed by rounding
            the	internal floating-point	values to the nearest integer
            values.

Notes

  If an	error is generated, no change is made to the contents of v.

Errors

  GL_INVALID_ENUM is generated if either target	or query is not	an accepted
  value.

  GL_INVALID_OPERATION is generated if glGetMap	is called between a call to
  glBegin and the corresponding	call to	glEnd.

See Also

  glEvalCoord, glMap1, glMap2

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glGetMaterial.3gl

Name

  glGetMaterialfv, glGetMaterialiv - return material parameters

C Specification

  void glGetMaterialfv(	GLenum face,
            GLenum pname,
            GLfloat	*params	)
  void glGetMaterialiv(	GLenum face,
            GLenum pname,
            GLint *params )

Parameters

  face	  Specifies which of the two materials is being	queried.  GL_FRONT or
      GL_BACK are accepted,	representing the front and back	materials,
      respectively.

  pname	  Specifies the	material parameter to return.  GL_AMBIENT,
      GL_DIFFUSE, GL_SPECULAR, GL_EMISSION,	GL_SHININESS, and
      GL_COLOR_INDEXES are accepted.

  params  Returns the requested	data.

Description

  glGetMaterial	returns	in params the value or values of parameter pname of
  material face.  Six parameters are defined:

  GL_AMBIENT	      params returns four integer or floating-point values
              representing the ambient reflectance of the material.
              Integer values, when requested, are linearly mapped
              from the internal	floating-point representation such
              that 1.0 maps to the most	positive representable
              integer value, and -1.0 maps to the most negative
              representable integer value.  If the internal value is
              outside the range	[-1,1],	the corresponding integer
              return value is undefined.

  GL_DIFFUSE	      params returns four integer or floating-point values
              representing the diffuse reflectance of the material.
              Integer values, when requested, are linearly mapped
              from the internal	floating-point representation such
              that 1.0 maps to the most	positive representable
              integer value, and -1.0 maps to the most negative
              representable integer value.  If the internal value is
              outside the range	[-1,1],	the corresponding integer
              return value is undefined.

  GL_SPECULAR	      params returns four integer or floating-point values
              representing the specular	reflectance of the material.
              Integer values, when requested, are linearly mapped
              from the internal	floating-point representation such
              that 1.0 maps to the most	positive representable
              integer value, and -1.0 maps to the most negative
              representable integer value.  If the internal value is
              outside the range	[-1,1],	the corresponding integer
              return value is undefined.

  GL_EMISSION	      params returns four integer or floating-point values
              representing the emitted light intensity of the
              material.	 Integer values, when requested, are linearly
              mapped from the internal floating-point representation
              such that	1.0 maps to the	most positive representable
              integer value, and -1.0 maps to the most negative
              representable integer value.  If the internal value is
              outside the range	[-1,1],	the corresponding integer
              return value is undefined.

  GL_SHININESS	      params returns one integer or floating-point value
              representing the specular	exponent of the	material.
              Integer values, when requested, are computed by
              rounding the internal floating-point value to the
              nearest integer value.

  GL_COLOR_INDEXES    params returns three integer or floating-point values
              representing the ambient,	diffuse, and specular indices
              of the material.	These indices are used only for	color
              index lighting.  (The other parameters are all used
              only for RGBA lighting.) Integer values, when
              requested, are computed by rounding the internal
              floating-point values to the nearest integer values.

Notes

  If an	error is generated, no change is made to the contents of params.

Errors

  GL_INVALID_ENUM is generated if face or pname	is not an accepted value.

  GL_INVALID_OPERATION is generated if glGetMaterial is	called between a call
  to glBegin and the corresponding call	to glEnd.

See Also

  glMaterial

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glGetPixelMap.3gl

Name

  glGetPixelMapfv, glGetPixelMapuiv, glGetPixelMapusv -	return the specified
  pixel	map

C Specification

  void glGetPixelMapfv(	GLenum map,
            GLfloat	*values	)
  void glGetPixelMapuiv( GLenum	map,
             GLuint	*values	)
  void glGetPixelMapusv( GLenum	map,
             GLushort *values )

Parameters

  map	  Specifies the	name of	the pixel map to return.  Accepted values are
      GL_PIXEL_MAP_I_TO_I, GL_PIXEL_MAP_S_TO_S, GL_PIXEL_MAP_I_TO_R,
      GL_PIXEL_MAP_I_TO_G, GL_PIXEL_MAP_I_TO_B, GL_PIXEL_MAP_I_TO_A,
      GL_PIXEL_MAP_R_TO_R, GL_PIXEL_MAP_G_TO_G, GL_PIXEL_MAP_B_TO_B, and
      GL_PIXEL_MAP_A_TO_A.

  values  Returns the pixel map	contents.

Description

  Please see the glPixelMap reference page for a description of	the
  acceptable values for	the map	parameter.  glGetPixelMap returns in values
  the contents of the pixel map	specified in map.  Pixel maps are used during
  the execution	of glReadPixels, glDrawPixels, glCopyPixels, glTexImage1D,
  and glTexImage2D to map color	indices, stencil indices, color	components,
  and depth components to other	values.

  Unsigned integer values, if requested, are linearly mapped from the
  internal fixed or floating-point representation such that 1.0	maps to	the
  largest representable	integer	value, and 0.0 maps to zero.  Return unsigned
  integer values are undefined if the map value	was not	in the range [0,1].

  To determine the required size of map, call glGet with the appropriate
  symbolic constant.

Notes

  If an	error is generated, no change is made to the contents of values.

Errors

  GL_INVALID_ENUM is generated if map is not an	accepted value.

  GL_INVALID_OPERATION is generated if glGetPixelMap is	called between a call
  to glBegin and the corresponding call	to glEnd.

Associated Gets

  glGet	with argument GL_PIXEL_MAP_I_TO_I_SIZE
  glGet	with argument GL_PIXEL_MAP_S_TO_S_SIZE
  glGet	with argument GL_PIXEL_MAP_I_TO_R_SIZE
  glGet	with argument GL_PIXEL_MAP_I_TO_G_SIZE
  glGet	with argument GL_PIXEL_MAP_I_TO_B_SIZE
  glGet	with argument GL_PIXEL_MAP_I_TO_A_SIZE
  glGet	with argument GL_PIXEL_MAP_R_TO_R_SIZE
  glGet	with argument GL_PIXEL_MAP_G_TO_G_SIZE
  glGet	with argument GL_PIXEL_MAP_B_TO_B_SIZE
  glGet	with argument GL_PIXEL_MAP_A_TO_A_SIZE
  glGet	with argument GL_MAX_PIXEL_MAP_TABLE

See Also

  glCopyPixels,	glDrawPixels, glPixelMap, glPixelTransfer, glReadPixels,
  glTexImage1D,	glTexImage2D

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glGetPointervEXT.3gl

Name

  glGetPointervEXT - return the	address	of a vertex data array

C Specification

  void glGetPointervEXT( GLenum	pname,
             GLvoid* *params )

Parameters

  pname	  Specifies the	array pointer to be returned.  Symbolic	constants
      GL_VERTEX_ARRAY_POINTER_EXT, GL_NORMAL_ARRAY_POINTER_EXT,
      GL_COLOR_ARRAY_POINTER_EXT, GL_INDEX_ARRAY_POINTER_EXT,
      GL_TEXTURE_COORD_ARRAY_POINTER_EXT, GL_EDGE_FLAG_ARRAY_POINTER_EXT,
      are accepted.

  params  returns the array pointer value specified by pname.

Description

  glGetPointervEXT returns array pointer information. pname is a symbolic
  constant indicating the array	pointer	to be returned,	and params is a
  pointer to a location	in which to place the returned data.

Notes

  The array pointers are client	side state.

  glGetPointervEXT is part of the EXT_vertex_array extension,	not
  part of the core GL command set. If "GL_EXT_vertex_array" is
  included in the string returned by glGetString, when called with argument
  GL_EXTENSIONS, extension EXT_vertex_array is supported.

Errors

  GL_INVALID_ENUM is generated if pname	is not an accepted value.

See Also

  glArrayElementEXT, glColorPointerEXT,	glDrawArraysEXT,
  glEdgeFlagPointerEXT,	glIndexPointerEXT, glNormalPointerEXT,
  glTexCoordPointerEXT,	glVertexPointerEXT

Introduction | Alphabetic | Specification

Last Edited: Fri Feb 24, 1995

AFV


glGetPolygonStipple.3gl

Name

  glGetPolygonStipple -	return the polygon stipple pattern

C Specification

  void glGetPolygonStipple( GLubyte *mask )

Parameters

  mask	Returns	the stipple pattern.

Description

  glGetPolygonStipple returns to mask a	32x32 polygon stipple pattern.	The
  pattern is packed into memory	as if glReadPixels with	both height and	width
  of 32, type of GL_BITMAP, and	format of GL_COLOR_INDEX were called, and the
  stipple pattern were stored in an internal 32x32 color index buffer.
  Unlike glReadPixels, however,	pixel transfer operations (shift, offset,
  pixel	map) are not applied to	the returned stipple image.

Notes

  If an	error is generated, no change is made to the contents of mask.

Errors

  GL_INVALID_OPERATION is generated if glGetPolygonStipple is executed
  between the execution	of glBegin and the corresponding execution of glEnd.

See Also

  glPixelStore,	glPixelTransfer, glPolygonStipple, glReadPixels

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glGetString.3gl

Name

  glGetString -	returns	a string describing the	current	GL connection

C Specification

  const	GLubyte	* glGetString( GLenum name )

Parameters

  name	Specifies a symbolic constant, one of GL_VENDOR, GL_RENDERER,
    GL_VERSION, or GL_EXTENSIONS.

Description

  glGetString returns a	pointer	to a static string describing some aspect of
  the current GL connection.  name can be one of the following:

  GL_VENDOR	      Returns the company responsible for this GL
              implementation.  This name does not change from release
              to release.

  GL_RENDERER	      Returns the name of the renderer.	 This name is
              typically	specific to a particular configuration of a
              hardware platform.  It does not change from release to
              release.

  GL_VERSION	      Returns a	version	or release number.

  GL_EXTENSIONS	      Returns a	space-separated	list of	supported extensions
              to GL.

  Because GL does not include queries for the performance characteristics of
  an implementation, it	is expected that some applications will	be written to
  recognize known platforms and	will modify their GL usage based on known
  performance characteristics of these platforms.  Strings GL_VENDOR and
  GL_RENDERER together uniquely	specify	a platform, and	will not change	from
  release to release.  They should be used by such platform recognition
  algorithms.

  Some applications will want to make use of features which are	not part of
  the standard GL.  These features may be implemented as extensions to the
  standard GL.	The GL_EXTENSIONS string is a space seperated list of
  supported GL extensions. (Extension names never contain a space character.)

  The GL_VERSION string	begins with a version number.  The version number is
  of the form major_number.minor_number	or
  major_number.minor_number.release_number. Vendor specific information	may
  follow the version number. Its format	depends	on the implementation, but a
  space	always seperates the version number and	the vendor specific
  information.

  All strings are null-terminated.

Notes

  If an	error is generated, glGetString	returns	zero.

  The client and server	may support different versions or extensions.
  glGetString always returns a compatible version number or list of
  extensions. The release number always	describes the server.

Errors

  GL_INVALID_ENUM is generated if name is not an accepted value.

  GL_INVALID_OPERATION is generated if glGetString is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glGetTexEnv.3gl

Name

  glGetTexEnvfv, glGetTexEnviv - return	texture	environment parameters

C Specification

  void glGetTexEnvfv( GLenum target,
              GLenum pname,
              GLfloat *params )
  void glGetTexEnviv( GLenum target,
              GLenum pname,
              GLint *params )

Parameters

  target  Specifies a texture environment.  Must be GL_TEXTURE_ENV.

  pname	  Specifies the	symbolic name of a texture environment parameter.
      Accepted values are GL_TEXTURE_ENV_MODE and GL_TEXTURE_ENV_COLOR.

  params  Returns the requested	data.

Description

  glGetTexEnv returns in params	selected values	of a texture environment that
  was specified	with glTexEnv.	target specifies a texture environment.
  Currently, only one texture environment is defined and supported:
  GL_TEXTURE_ENV.

  pname	names a	specific texture environment parameter.	 The two parameters
  are as follows:

  GL_TEXTURE_ENV_MODE
        params returns the single-valued texture environment mode, a
        symbolic constant.

  GL_TEXTURE_ENV_COLOR
        params returns four	integer	or floating-point values that are the
        texture environment	color.	Integer	values,	when requested,	are
        linearly mapped from the internal floating-point representation
        such that 1.0 maps to the most positive representable integer,
        and	-1.0 maps to the most negative representable integer.

Notes

  If an	error is generated, no change is made to the contents of params.

Errors

  GL_INVALID_ENUM is generated if target or pname is not an accepted value.

  GL_INVALID_OPERATION is generated if glGetTexEnv is called between a call
  to glBegin and the corresponding call	to glEnd.

See Also

  glTexEnv

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glGetTexGen.3gl

Name

  glGetTexGendv, glGetTexGenfv,	glGetTexGeniv -	return texture coordinate
  generation parameters

C Specification

  void glGetTexGendv( GLenum coord,
              GLenum pname,
              GLdouble *params )
  void glGetTexGenfv( GLenum coord,
              GLenum pname,
              GLfloat *params )
  void glGetTexGeniv( GLenum coord,
              GLenum pname,
              GLint *params )

Parameters

  coord	  Specifies a texture coordinate.  Must	be GL_S, GL_T, GL_R, or	GL_Q.

  pname	  Specifies the	symbolic name of the value(s) to be returned.  Must
      be either GL_TEXTURE_GEN_MODE	or the name of one of the texture
      generation plane equations: GL_OBJECT_PLANE or GL_EYE_PLANE.

  params  Returns the requested	data.

Description

  glGetTexGen returns in params	selected parameters of a texture coordinate
  generation function that was specified using glTexGen.  coord	names one of
  the (s,t,r,q)	texture	coordinates, using the symbolic	constant GL_S, GL_T,
  GL_R,	or GL_Q.

  pname	specifies one of three symbolic	names:

  GL_TEXTURE_GEN_MODE		params returns the single-valued texture
                generation function, a symbolic	constant.

  GL_OBJECT_PLANE		params returns the four	plane equation
                coefficients that specify object linear-
                coordinate generation.	Integer	values,	when
                requested, are mapped directly from the
                internal floating-point	representation.

  GL_EYE_PLANE			params returns the four	plane equation
                coefficients that specify eye linear-
                coordinate generation.	Integer	values,	when
                requested, are mapped directly from the
                internal floating-point	representation.	 The
                returned values	are those maintained in	eye
                coordinates.  They are not equal to the
                values specified using glTexGen, unless	the
                modelview matrix was identity at the time
                glTexGen was called.

Notes

  If an	error is generated, no change is made to the contents of params.

Errors

  GL_INVALID_ENUM is generated if coord	or pname is not	an accepted value.

  GL_INVALID_OPERATION is generated if glGetTexGen is called between a call
  to glBegin and the corresponding call	to glEnd.

See Also

  glTexGen

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glGetTexImage.3gl

Name

  glGetTexImage	- return a texture image

C Specification

  void glGetTexImage( GLenum target,
              GLint level,
              GLenum format,
              GLenum type,
              GLvoid *pixels )

Parameters

  target  Specifies which texture is to	be obtained.  GL_TEXTURE_1D and
      GL_TEXTURE_2D	are accepted.

  level	  Specifies the	level-of-detail	number of the desired image.  Level 0
      is the base image level.  Level n is the nth mipmap reduction
      image.

  format  Specifies a pixel format for the returned data.  The supported
      formats are GL_RED, GL_GREEN,	GL_BLUE, GL_ALPHA, GL_RGB, GL_RGBA,
      GL_LUMINANCE,	and GL_LUMINANCE_ALPHA.

  type	  Specifies a pixel type for the returned data.	 The supported types
      are GL_UNSIGNED_BYTE,	GL_BYTE, GL_UNSIGNED_SHORT, GL_SHORT,
      GL_UNSIGNED_INT, GL_INT, and GL_FLOAT.

  pixels  Returns the texture image.  Should be	a pointer to an	array of the
      type specified by type.

Description

  glGetTexImage	returns	a texture image	into pixels.  target specifies
  whether the desired texture image is one specified by	glTexImage1D
  (GL_TEXTURE_1D) or by	glTexImage2D (GL_TEXTURE_2D).  level specifies the
  level-of-detail number of the	desired	image.	format and type	specify	the
  format and type of the desired image array.  Please see the reference	pages
  glTexImage1D and glDrawPixels	for a description of the acceptable values
  for the format and type parameters, respectively.

  Operation of glGetTexImage is	best understood	by considering the selected
  internal four-component texture image	to be an RGBA color buffer the size
  of the image.	 The semantics of glGetTexImage	are then identical to those
  of glReadPixels called with the same format and type,	with x and y set to
  zero,	width set to the width of the texture image (including border if one
  was specified), and height set to one	for 1-D	images,	or to the height of
  the texture image (including border if one was specified) for	2-D images.
  Because the internal texture image is	an RGBA	image, pixel formats
  GL_COLOR_INDEX, GL_STENCIL_INDEX, and	GL_DEPTH_COMPONENT are not accepted,
  and pixel type GL_BITMAP is not accepted.

  If the selected texture image	does not contain four components, the
  following mappings are applied.  Single-component textures are treated as
  RGBA buffers with red	set to the single-component value, and green, blue,
  and alpha set	to zero.  Two-component	textures are treated as	RGBA buffers
  with red set to the value of component zero, alpha set to the	value of
  component one, and green and blue set	to zero.  Finally, three-component
  textures are treated as RGBA buffers with red	set to component zero, green
  set to component one,	blue set to component two, and alpha set to zero.

  To determine the required size of pixels, use	glGetTexLevelParameter to
  ascertain the	dimensions of the internal texture image, then scale the
  required number of pixels by the storage required for	each pixel, based on
  format and type.  Be sure to take the	pixel storage parameters into
  account, especially GL_PACK_ALIGNMENT.

Notes

  If an	error is generated, no change is made to the contents of pixels.

Errors

  GL_INVALID_ENUM is generated if target, format, or type is not an accepted
  value.

  GL_INVALID_VALUE is generated	if level is less than zero or greater than
  log max, where max is	the returned value of GL_MAX_TEXTURE_SIZE.
     2
  GL_INVALID_OPERATION is generated if glGetTexImage is	executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGetTexLevelParameter with argument GL_TEXTURE_WIDTH
  glGetTexLevelParameter with argument GL_TEXTURE_HEIGHT
  glGetTexLevelParameter with argument GL_TEXTURE_BORDER
  glGetTexLevelParameter with argument GL_TEXTURE_COMPONENTS
  glGet	with arguments GL_PACK_ALIGNMENT and others

See Also

  glDrawPixels,	glReadPixels, glTexImage1D, glTexImage2D

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glGetTexLevelParameter.3gl

Name

  glGetTexLevelParameterfv, glGetTexLevelParameteriv - return texture
  parameter values for a specific level	of detail

C Specification

  void glGetTexLevelParameterfv( GLenum	target,
                 GLint level,
                 GLenum	pname,
                 GLfloat *params )
  void glGetTexLevelParameteriv( GLenum	target,
                 GLint level,
                 GLenum	pname,
                 GLint *params )

Parameters

  target  Specifies the	symbolic name of the target texture, either
      GL_TEXTURE_1D	or GL_TEXTURE_2D.

  level	  Specifies the	level-of-detail	number of the desired image.  Level 0
      is the base image level.  Level n is the nth mipmap reduction
      image.

  pname	  Specifies the	symbolic name of a texture parameter.
      GL_TEXTURE_WIDTH, GL_TEXTURE_HEIGHT, GL_TEXTURE_COMPONENTS, and
      GL_TEXTURE_BORDER are	accepted.

  params  Returns the requested	data.

Description

  glGetTexLevelParameter returns in params texture parameter values for	a
  specific level-of-detail value, specified as level.  target defines the
  target texture, either GL_TEXTURE_1D or GL_TEXTURE_2D, to specify one- or
  two-dimensional texturing.  pname specifies the texture parameter whose
  value	or values will be returned.

  The accepted parameter names are as follows:

  GL_TEXTURE_WIDTH
        params returns a single value, the width of	the texture image.
        This value includes	the border of the texture image.

  GL_TEXTURE_HEIGHT
        params returns a single value, the height of the texture image.
        This value includes	the border of the texture image.

  GL_TEXTURE_COMPONENTS
        params returns a single value, the number of components in the
        texture image.

  GL_TEXTURE_BORDER
        params returns a single value, the width in	pixels of the border
        of the texture image.

Notes

  If an	error is generated, no change is made to the contents of params.

Errors

  GL_INVALID_ENUM is generated if target or pname is not an accepted value.

  GL_INVALID_VALUE is generated	if level is less than zero or greater than
  log max, where max is	the returned value of GL_MAX_TEXTURE_SIZE.
     2
  GL_INVALID_OPERATION is generated if glGetTexLevelParameter is called
  between a call to glBegin and	the corresponding call to glEnd.

See Also

  glGetTexParameter, glTexImage1D, glTexImage2D, glTexParameter

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glGetTexParameter.3gl

Name

  glGetTexParameterfv, glGetTexParameteriv - return texture parameter values

C Specification

  void glGetTexParameterfv( GLenum target,
                GLenum pname,
                GLfloat *params )
  void glGetTexParameteriv( GLenum target,
                GLenum pname,
                GLint *params )

Parameters

  target  Specifies the	symbolic name of the target texture.  GL_TEXTURE_1D
      and GL_TEXTURE_2D are	accepted.

  pname	  Specifies the	symbolic name of a texture parameter.
      GL_TEXTURE_MAG_FILTER, GL_TEXTURE_MIN_FILTER,	GL_TEXTURE_WRAP_S,
      GL_TEXTURE_WRAP_T, and GL_TEXTURE_BORDER_COLOR are accepted.

  params  Returns the texture parameters.

Description

  glGetTexParameter returns in params the value	or values of the texture
  parameter specified as pname.	 target	defines	the target texture, either
  GL_TEXTURE_1D	or GL_TEXTURE_2D, to specify one- or two-dimensional
  texturing.  pname accepts the	same symbols as	glTexParameter,	with the same
  interpretations:

  GL_TEXTURE_MAG_FILTER		     Returns the single-valued texture
                     magnification filter, a symbolic
                     constant.

  GL_TEXTURE_MIN_FILTER		     Returns the single-valued texture
                     minification filter, a symbolic
                     constant.

  GL_TEXTURE_WRAP_S		     Returns the single-valued wrapping
                     function for texture coordinate s,	a
                     symbolic constant.

  GL_TEXTURE_WRAP_T		     Returns the single-valued wrapping
                     function for texture coordinate t,	a
                     symbolic constant.

  GL_TEXTURE_BORDER_COLOR	     Returns four integer or floating-point
                     numbers that comprise the RGBA color of
                     the texture border.  Floating-point
                     values are	returned in the	range [0,1].
                     Integer values are	returned as a linear
                     mapping of	the internal floating-point
                     representation such that 1.0 maps to the
                     most positive representable integer and
                     -1.0 maps to the most negative
                     representable integer.

Notes

  If an	error is generated, no change is made to the contents of params.

Errors

  GL_INVALID_ENUM is generated if target or pname is not an accepted value.

  GL_INVALID_OPERATION is generated if glGetTexParameter is called between a
  call to glBegin and the corresponding	call to	glEnd.

See Also

  glTexParameter

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glHint.3gl

Name

  glHint - specify implementation-specific hints

C Specification

  void glHint( GLenum target,
           GLenum mode )

Parameters

  target  Specifies a symbolic constant	indicating the behavior	to be
      controlled.  GL_FOG_HINT,  GL_LINE_SMOOTH_HINT,
      GL_PERSPECTIVE_CORRECTION_HINT, GL_POINT_SMOOTH_HINT,	and
      GL_POLYGON_SMOOTH_HINT are accepted.

  mode	  Specifies a symbolic constant	indicating the desired behavior.
      GL_FASTEST, GL_NICEST, and GL_DONT_CARE are accepted.

Description

  Certain aspects of GL	behavior, when there is	room for interpretation, can
  be  controlled with hints.  A	hint is	specified with two arguments.  target
  is a symbolic	constant indicating the	behavior to be controlled, and mode
  is another symbolic constant indicating the desired behavior.	 mode can be
  one of the following:

  GL_FASTEST	      The most efficient option	should be chosen.

  GL_NICEST	      The most correct,	or highest quality, option should be
              chosen.

  GL_DONT_CARE	      The client doesn't have a	preference.

  Though the implementation aspects that can be	hinted are well	defined, the
  interpretation of the	hints depends on the implementation.  The hint
  aspects that can be specified	with target, along with	suggested semantics,
  are as follows:

  GL_FOG_HINT	      Indicates	the accuracy of	fog calculation.  If per-
              pixel fog	calculation is not efficiently supported by
              the GL implementation, hinting GL_DONT_CARE or
              GL_FASTEST can result in per-vertex calculation of fog
              effects.

  GL_LINE_SMOOTH_HINT Indicates	the sampling quality of	antialiased lines.
              Hinting GL_NICEST	can result in more pixel fragments
              being generated during rasterization, if a larger
              filter function is applied.

  GL_PERSPECTIVE_CORRECTION_HINT
              Indicates	the quality of color and texture coordinate
              interpolation.  If perspective-corrected parameter
              interpolation is not efficiently supported by the	GL
              implementation, hinting GL_DONT_CARE or GL_FASTEST can
              result in	simple linear interpolation of colors and/or
              texture coordinates.

  GL_POINT_SMOOTH_HINT
              Indicates	the sampling quality of	antialiased points.
              Hinting GL_NICEST	can result in more pixel fragments
              being generated during rasterization, if a larger
              filter function is applied.

  GL_POLYGON_SMOOTH_HINT
              Indicates	the sampling quality of	antialiased polygons.
              Hinting GL_NICEST	can result in more pixel fragments
              being generated during rasterization, if a larger
              filter function is applied.

Notes

  The interpretation of	hints depends on the implementation.  glHint can be
  ignored.

Errors

  GL_INVALID_ENUM is generated if either target	or mode	is not an accepted
  value.

  GL_INVALID_OPERATION is generated if glHint is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glIndex.3gl

Name

  glIndexd, glIndexf, glIndexi,	glIndexs, glIndexdv, glIndexfv,	glIndexiv,
  glIndexsv - set the current color index

C Specification

  void glIndexd( GLdouble c )
  void glIndexf( GLfloat c )
  void glIndexi( GLint c )
  void glIndexs( GLshort c )

Parameters

  c  Specifies the new value for the current color index.

C Specification

  void glIndexdv( const	GLdouble *c )
  void glIndexfv( const	GLfloat	*c )
  void glIndexiv( const	GLint *c )
  void glIndexsv( const	GLshort	*c )

Parameters

  c    Specifies a pointer to a	one-element array that contains	the new	value
       for the current color index.

Description

  glIndex updates the current (single-valued) color index.  It takes one
  argument: the	new value for the current color	index.

  The current index is stored as a floating-point value.  Integer values are
  converted directly to	floating-point values, with no special mapping.

  Index	values outside the representable range of the color index buffer are
  not clamped.	However, before	an index is dithered (if enabled) and written
  to the frame buffer, it is converted to fixed-point format.  Any bits	in
  the integer portion of the resulting fixed-point value that do not
  correspond to	bits in	the frame buffer are masked out.

Notes

  The current index can	be updated at any time.	 In particular,	glIndex	can
  be called between a call to glBegin and the corresponding call to glEnd.

Associated Gets

  glGet	with argument GL_CURRENT_INDEX

See Also

  glColor

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glIndexMask.3gl

Name

  glIndexMask -	control	the writing of individual bits in the color index
  buffers

C Specification

  void glIndexMask( GLuint mask	)

Parameters

  mask	Specifies a bit	mask to	enable and disable the writing of individual
    bits in	the color index	buffers.  Initially, the mask is all ones.

Description

  glIndexMask controls the writing of individual bits in the color index
  buffers.  The	least significant n bits of mask, where	n is the number	of
  bits in a color index	buffer,	specify	a mask.	 Wherever a one	appears	in
  the mask, the	corresponding bit in the color index buffer (or	buffers) is
  made writable.  Where	a zero appears,	the bit	is write-protected.

  This mask is used only in color index	mode, and it affects only the buffers
  currently selected for writing (see glDrawBuffer).  Initially, all bits are
  enabled for writing.

Errors

  GL_INVALID_OPERATION is generated if glIndexMask is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_INDEX_WRITEMASK

See Also

  glColorMask, glDepthMask, glDrawBuffer, glIndex, glStencilMask

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glIndexPointerEXT.3gl

Name

  glIndexPointerEXT - define an	array of color indexes

C Specification

  void glIndexPointerEXT( GLenum type,
              GLsizei stride,
              GLsizei count,
              const	GLvoid *pointer	)

Parameters

  type	   Specifies the data type of each color index in the array.
       Symbolic constants GL_SHORT,	GL_INT,	GL_FLOAT, or GL_DOUBLE_EXT,
       are accepted.

  stride   Specifies the byte offset between consecutive color indexes.	 If
       stride is zero the color indexes are	understood to be tightly
       packed in the array.

  count	   Specifies the number	of indexes, counting from the first, that are
       static.

  pointer  Specifies a pointer to the first index in the array.

Description

  glIndexPointerEXT specifies the location and data format of an array of
  color	indexes	to use when rendering using the vertex array extension. type
  specifies the data type of each color index and stride gives the byte
  stride from one color index to the next allowing vertexes and attributes to
  be packed into a single array or stored in separate arrays. (Single-array
  storage may be more efficient on some implementations.) count indicates
  the number of array elements (counting from the first) that are static.
  Static elements may be modified by the application, but once they are
  modified, the application must explicitly respecify the array before using
  it for any rendering. When a color index array is specified, type, stride,
  count and pointer are saved as client-side state, and static array elements
  may be cached by the implementation.

  The color index array	is enabled and disabled	using glEnable and glDisable
  with the argument GL_INDEX_ARRAY_EXT.	If enabled, the	color index array is
  used when glDrawArraysEXT or glArrayElementEXT is called.

  Use glDrawArraysEXT to define	a sequence of primitives (all of the same
  type)	from pre-specified vertex and vertex attribute arrays.	Use
  glArrayElementEXT to specify primitives by indexing vertexes and vertex
  attributes.

Notes

  Non-static array elements are	not accessed until glArrayElementEXT or
  glDrawArraysEXT is executed.

  By default the color index array is disabled and it won't be accessed	when
  glArrayElementEXT or glDrawArraysEXT is called.

  Although it is not an error to call glIndexPointerEXT between the
  execution of glBegin and the corresponding execution of glEnd, the results
  are undefined.

  glIndexPointerEXT will typically be implemented on the client	side with no
  protocol.

  Since	the color index	array parameters are client side state,	they are not
  saved	or restored by glPushAttrib and	glPopAttrib.

  glIndexPointerEXT commands are not entered into display lists.

  glIndexPointerEXT is part of the EXT_vertex_array extension, not
  part of the core GL command set. If "GL_EXT_vertex_array" is
  included in the string returned by glGetString, when called with argument
  GL_EXTENSIONS, extension EXT_vertex_array is supported.

Errors

  GL_INVALID_ENUM is generated if type is not an accepted value.

  GL_INVALID_VALUE is generated	if stride or count is negative.

Associated Gets

  glIsEnabled with argument GL_INDEX_ARRAY_EXT
  glGet	with argument GL_INDEX_ARRAY_SIZE_EXT
  glGet	with argument GL_INDEX_ARRAY_TYPE_EXT
  glGet	with argument GL_INDEX_ARRAY_STRIDE_EXT
  glGet	with argument GL_INDEX_ARRAY_COUNT_EXT
  glGetPointervEXT with	argument GL_INDEX_ARRAY_POINTER_EXT

See Also

  glArrayElementEXT, glColorPointerEXT,	glDrawArraysEXT,
  glEdgeFlagPointerEXT,	glGetPointervEXT, glNormalPointerEXT,
  glTexCoordPointerEXT,	glVertexPointerEXT, glEnable

Introduction | Alphabetic | Specification

Last Edited: Fri Feb 24, 1995

AFV


glInitNames.3gl

Name

  glInitNames -	initialize the name stack

C Specification

  void glInitNames( void )

Description

  The name stack is used during	selection mode to allow	sets of	rendering
  commands to be uniquely identified.  It consists of an ordered set of
  unsigned integers.  glInitNames causes the name stack	to be initialized to
  its default empty state.

  The name stack is always empty while the render mode is not GL_SELECT.
  Calls	to glInitNames while the render	mode is	not GL_SELECT are ignored.

Errors

  GL_INVALID_OPERATION is generated if glInitNames is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_NAME_STACK_DEPTH
  glGet	with argument GL_MAX_NAME_STACK_DEPTH

See Also

  glLoadName, glPushName, glRenderMode,	glSelectBuffer

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glIsEnabled.3gl

Name

  glIsEnabled -	test whether a capability is enabled

C Specification

  GLboolean glIsEnabled( GLenum	cap )

Parameters

  cap  Specifies a symbolic constant indicating	a GL capability.

Description

  glIsEnabled returns GL_TRUE if cap is	an enabled capability and returns
  GL_FALSE otherwise.  The following capabilities are accepted for cap:

  GL_ALPHA_TEST		       See glAlphaFunc.
  GL_AUTO_NORMAL	       See glEvalCoord.
  GL_BLEND		       See glBlendFunc.
  GL_CLIP_PLANEi	       See glClipPlane.
  GL_COLOR_ARRAY_EXT	       See glColorPointerEXT.
  GL_COLOR_MATERIAL	       See glColorMaterial.
  GL_CULL_FACE		       See glCullFace.
  GL_DEPTH_TEST		       See glDepthFunc and  glDepthRange.
  GL_DITHER		       See glEnable.
  GL_EDGE_FLAG_ARRAY_EXT       See glEdgeFlagPointerEXT.
  GL_FOG		       See glFog.
  GL_INDEX_ARRAY_EXT	       See glIndexPointerEXT.
  GL_LIGHTi		       See glLightModel	and glLight.
  GL_LIGHTING		       See glMaterial, glLightModel, and glLight.
  GL_LINE_SMOOTH	       See glLineWidth.
  GL_LINE_STIPPLE	       See glLineStipple.
  GL_LOGIC_OP		       See glLogicOp.
  GL_MAP1_COLOR_4	       See glMap1.
  GL_MAP1_INDEX		       See glMap1.
  GL_MAP1_NORMAL	       See glMap1.
  GL_MAP1_TEXTURE_COORD_1      See glMap1.
  GL_MAP1_TEXTURE_COORD_2      See glMap1.
  GL_MAP1_TEXTURE_COORD_3      See glMap1.
  GL_MAP1_TEXTURE_COORD_4      See glMap1.
  GL_MAP1_VERTEX_3	       See glMap1.
  GL_MAP1_VERTEX_4	       See glMap1.
  GL_MAP2_COLOR_4	       See glMap2.
  GL_MAP2_INDEX		       See glMap2.
  GL_MAP2_NORMAL	       See glMap2.
  GL_MAP2_TEXTURE_COORD_1      See glMap2.
  GL_MAP2_TEXTURE_COORD_2      See glMap2.
  GL_MAP2_TEXTURE_COORD_3      See glMap2.
  GL_MAP2_TEXTURE_COORD_4      See glMap2.
  GL_MAP2_VERTEX_3	       See glMap2.
  GL_MAP2_VERTEX_4	       See glMap2.
  GL_NORMAL_ARRAY_EXT	       See glNormalPointerEXT.
  GL_NORMALIZE		       See glNormal.
  GL_POINT_SMOOTH	       See glPointSize.
  GL_POLYGON_SMOOTH	       See glPolygonMode.
  GL_POLYGON_STIPPLE	       See glPolygonStipple.
  GL_SCISSOR_TEST	       See glScissor.
  GL_STENCIL_TEST	       See glStencilFunc and glStencilOp.
  GL_TEXTURE_1D		       See glTexImage1D.
  GL_TEXTURE_2D		       See glTexImage2D.
  GL_TEXTURE_COORD_ARRAY_EXT   See glTexCoordPointerEXT.
  GL_TEXTURE_GEN_Q	       See glTexGen.
  GL_TEXTURE_GEN_R	       See glTexGen.
  GL_TEXTURE_GEN_S	       See glTexGen.
  GL_TEXTURE_GEN_T	       See glTexGen.
  GL_VERTEX_ARRAY_EXT	       See glVertexPointerEXT.

Notes

  If an	error is generated, glIsEnabled	returns	zero.

Errors

  GL_INVALID_ENUM is generated if cap is not an	accepted value.

  GL_INVALID_OPERATION is generated if glIsEnabled is executed between the
  execution of glBegin and the corresponding execution of glEnd.

See Also

  glEnable

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glIsList.3gl

Name

  glIsList - test for display-list existence

C Specification

  GLboolean glIsList( GLuint list )

Parameters

  list	Specifies a potential display-list name.

Description

  glIsList returns GL_TRUE if list is the name of a display list and returns
  GL_FALSE otherwise.

Errors

  GL_INVALID_OPERATION is generated if glIsList	is executed between the
  execution of	glBegin	and the	corresponding execution	of glEnd.

See Also

  glCallList, glCallLists, glDeleteLists, glGenLists, glNewList

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glLight.3gl

Name

  glLightf, glLighti, glLightfv, glLightiv - set light source parameters

C Specification

  void glLightf( GLenum	light,
         GLenum	pname,
         GLfloat param )
  void glLighti( GLenum	light,
         GLenum	pname,
         GLint param )

Parameters

  light	  Specifies a light.  The number of lights is depends on the
      implementation, but at least eight lights are	supported.  They are
      identified by	symbolic names of the form GL_LIGHTi where 0 <= i <
      GL_MAX_LIGHTS.

  pname	  Specifies a single-valued light source parameter for light.
      GL_SPOT_EXPONENT, GL_SPOT_CUTOFF, GL_CONSTANT_ATTENUATION,
      GL_LINEAR_ATTENUATION	, and GL_QUADRATIC_ATTENUATION are accepted.

  param	  Specifies the	value that parameter pname of light source light will
      be set to.

C Specification

  void glLightfv( GLenum light,
          GLenum pname,
          const	GLfloat	*params	)
  void glLightiv( GLenum light,
          GLenum pname,
          const	GLint *params )

Parameters

  light
       Specifies a light.  The number of lights	depends	on the
       implementation, but at least eight lights are supported.	 They are
       identified by symbolic names of the form	GL_LIGHTi where	0 <= i <
       GL_MAX_LIGHTS.

  pname
       Specifies a light source	parameter for light.  GL_AMBIENT, GL_DIFFUSE,
       GL_SPECULAR, GL_POSITION, GL_SPOT_DIRECTION, GL_SPOT_EXPONENT,
       GL_SPOT_CUTOFF, GL_CONSTANT_ATTENUATION,	GL_LINEAR_ATTENUATION, and
       GL_QUADRATIC_ATTENUATION	are accepted.

  params
       Specifies a pointer to the value	or values that parameter pname of
       light source light will be set to.

Description

  glLight sets the values of individual	light source parameters.  light	names
  the light and	is a symbolic name of the form GL_LIGHTi, where	0 <= i <
  GL_MAX_LIGHTS.  pname	specifies one of ten light source parameters, again
  by symbolic name.  params is either a	single value or	a pointer to an	array
  that contains	the new	values.

  Lighting calculation is enabled and disabled using glEnable and glDisable
  with argument	GL_LIGHTING.  When lighting is enabled,	light sources that
  are enabled contribute to the	lighting calculation.  Light source i is
  enabled and disabled using glEnable and glDisable with argument GL_LIGHTi.

  The ten light	parameters are as follows:

  GL_AMBIENT	      params contains four integer or floating-point values
              that specify the ambient RGBA intensity of the light.
              Integer values are mapped	linearly such that the most
              positive representable value maps	to 1.0,	and the	most
              negative representable value maps	to -1.0.  Floating-
              point values are mapped directly.	 Neither integer nor
              floating-point values are	clamped.  The default ambient
              light intensity is (0.0, 0.0, 0.0, 1.0).

  GL_DIFFUSE	      params contains four integer or floating-point values
              that specify the diffuse RGBA intensity of the light.
              Integer values are mapped	linearly such that the most
              positive representable value maps	to 1.0,	and the	most
              negative representable value maps	to -1.0.  Floating-
              point values are mapped directly.	 Neither integer nor
              floating-point values are	clamped.  The default diffuse
              intensity	is (0.0, 0.0, 0.0, 1.0)	for all	lights other
              than light zero.	The default diffuse intensity of
              light zero is (1.0, 1.0, 1.0, 1.0).

  GL_SPECULAR	      params contains four integer or floating-point values
              that specify the specular	RGBA intensity of the light.
              Integer values are mapped	linearly such that the most
              positive representable value maps	to 1.0,	and the	most
              negative representable value maps	to -1.0.  Floating-
              point values are mapped directly.	 Neither integer nor
              floating-point values are	clamped.  The default
              specular intensity is (0.0, 0.0, 0.0, 1.0) for all
              lights other than	light zero.  The default specular
              intensity	of light zero is (1.0, 1.0, 1.0, 1.0).

  GL_POSITION	      params contains four integer or floating-point values
              that specify the position	of the light in	homogeneous
              object coordinates.  Both	integer	and floating-point
              values are mapped	directly.  Neither integer nor
              floating-point values are	clamped.

              The position is transformed by the modelview matrix
              when glLight is called (just as if it were a point),
              and it is	stored in eye coordinates.  If the w
              component	of the position	is 0.0,	the light is treated
              as a directional source.	Diffuse	and specular lighting
              calculations take	the light's direction, but not its
              actual position, into account, and attenuation is
              disabled.	 Otherwise, diffuse and	specular lighting
              calculations are based on	the actual location of the
              light in eye coordinates,	and attenuation	is enabled.
              The default position is (0,0,1,0); thus, the default
              light source is directional, parallel to,	and in the
              direction	of the -z axis.

  GL_SPOT_DIRECTION   params contains three integer or floating-point values
              that specify the direction of the	light in homogeneous
              object coordinates.  Both	integer	and floating-point
              values are mapped	directly.  Neither integer nor
              floating-point values are	clamped.

              The spot direction is transformed	by the inverse of the
              modelview	matrix when glLight is called (just as it it
              were a normal), and it is	stored in eye coordinates.
              It is significant	only when GL_SPOT_CUTOFF is not	180,
              which it is by default.  The default direction is
              (0,0,-1).

  GL_SPOT_EXPONENT    params is	a single integer or floating-point value that
              specifies	the intensity distribution of the light.
              Integer and floating-point values	are mapped directly.
              Only values in the range [0,128] are accepted.

              Effective	light intensity	is attenuated by the cosine
              of the angle between the direction of the	light and the
              direction	from the light to the vertex being lighted,
              raised to	the power of the spot exponent.	 Thus, higher
              spot exponents result in a more focused light source,
              regardless of the	spot cutoff angle (see next
              paragraph).  The default spot exponent is	0, resulting
              in uniform light distribution.

  GL_SPOT_CUTOFF      params is	a single integer or floating-point value that
              specifies	the maximum spread angle of a light source.
              Integer and floating-point values	are mapped directly.
              Only values in the range [0,90], and the special value
              180, are accepted.  If the angle between the direction
              of the light and the direction from the light to the
              vertex being lighted is greater than the spot cutoff
              angle, the light is completely masked.  Otherwise, its
              intensity	is controlled by the spot exponent and the
              attenuation factors.  The	default	spot cutoff is 180,
              resulting	in uniform light distribution.

  GL_CONSTANT_ATTENUATION

  GL_LINEAR_ATTENUATION

  GL_QUADRATIC_ATTENUATION
              params is	a single integer or floating-point value that
              specifies	one of the three light attenuation factors.
              Integer and floating-point values	are mapped directly.
              Only nonnegative values are accepted.  If	the light is
              positional, rather than directional, its intensity is
              attenuated by the	reciprocal of the sum of: the
              constant factor, the linear factor times the distance
              between the light	and the	vertex being lighted, and the
               quadratic factor	times the square of the	same
              distance.	 The default attenuation factors are (1,0,0),
              resulting	in no attenuation.

Notes

  It is	always the case	that GL_LIGHTi = GL_LIGHT0 + i.

Errors

  GL_INVALID_ENUM is generated if either light or pname	is not an accepted
  value.

  GL_INVALID_VALUE is generated	if a spot exponent value is specified outside
  the range [0,128], or	if spot	cutoff is specified outside the	range [0,90]
  (except for the special value	180), or if a negative attenuation factor is
  specified.

  GL_INVALID_OPERATION is generated if glLight is executed between the
  execution of	glBegin	and the	corresponding execution	of glEnd.

Associated Gets

  glGetLight
  glIsEnabled with argument GL_LIGHTING

See Also

  glColorMaterial, glLightModel, glMaterial

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glLightModel.3gl

Name

  glLightModelf, glLightModeli,	glLightModelfv,	glLightModeliv - set the
  lighting model parameters

C Specification

  void glLightModelf( GLenum pname,
              GLfloat param )
  void glLightModeli( GLenum pname,
              GLint param )

Parameters

  pname	  Specifies a single-valued lighting model parameter.
      GL_LIGHT_MODEL_LOCAL_VIEWER and GL_LIGHT_MODEL_TWO_SIDE are
      accepted.

  param	  Specifies the	value that param will be set to.

C Specification

  void glLightModelfv( GLenum pname,
               const GLfloat *params )
  void glLightModeliv( GLenum pname,
               const GLint *params )

Parameters

  pname
       Specifies a lighting model parameter.  GL_LIGHT_MODEL_AMBIENT,
       GL_LIGHT_MODEL_LOCAL_VIEWER, and	GL_LIGHT_MODEL_TWO_SIDE	are accepted.

  params
       Specifies a pointer to the value	or values that params will be set to.

Description

  glLightModel sets the	lighting model parameter.  pname names a parameter
  and params gives the new value.  There are three lighting model parameters:

  GL_LIGHT_MODEL_AMBIENT
        params contains four integer or floating-point values that
        specify the	ambient	RGBA intensity of the entire scene.  Integer
        values are mapped linearly such that the most positive
        representable value	maps to	1.0, and the most negative
        representable value	maps to	-1.0.  Floating-point values are
        mapped directly.  Neither integer nor floating-point values	are
        clamped.  The default ambient scene	intensity is (0.2, 0.2,	0.2,
        1.0).

  GL_LIGHT_MODEL_LOCAL_VIEWER
        params is a	single integer or floating-point value that specifies
        how	specular reflection angles are computed.  If params is 0 (or
        0.0), specular reflection angles take the view direction to	be
        parallel to	and in the direction of	the -z axis, regardless	of
        the	location of the	vertex in eye coordinates.  Otherwise
        specular reflections are computed from the origin of the eye
        coordinate system.	The default is 0.

  GL_LIGHT_MODEL_TWO_SIDE
        params is a	single integer or floating-point value that specifies
        whether one- or two-sided lighting calculations are	done for
        polygons.  It has no effect	on the lighting	calculations for
        points, lines, or bitmaps.	If params is 0 (or 0.0), one-sided
        lighting is	specified, and only the	front material parameters are
        used in the	lighting equation.  Otherwise, two-sided lighting is
        specified.	In this	case, vertices of back-facing polygons are
        lighted using the back material parameters,	and have their
        normals reversed before the	lighting equation is evaluated.
        Vertices of	front-facing polygons are always lighted using the
        front material parameters, with no change to their normals.	 The
        default is 0.

  In RGBA mode,	the lighted color of a vertex is the sum of the	material
  emission intensity, the product of the material ambient reflectance and the
  lighting model full-scene ambient intensity, and the contribution of each
  enabled light	source.	 Each light source contributes the sum of three
  terms: ambient, diffuse, and specular.  The ambient light source
  contribution is the product of the material ambient reflectance and the
  light's ambient intensity.  The diffuse light	source contribution is the
  product of the material diffuse reflectance, the light's diffuse intensity,
  and the dot product of the vertex's normal with the normalized vector	from
  the vertex to	the light source.  The specular	light source contribution is
  the product of the material specular reflectance, the	light's	specular
  intensity, and the dot product of the	normalized vertex-to-eye and vertex-
  to-light vectors, raised to the power	of the shininess of the	material.
  All three light source contributions are attenuated equally based on the
  distance from	the vertex to the light	source and on light source direction,
  spread exponent, and spread cutoff angle.  All dot products are replaced
  with zero if they evaluate to	a negative value.

  The alpha component of the resulting lighted color is	set to the alpha
  value	of the material	diffuse	reflectance.

  In color index mode, the value of the	lighted	index of a vertex ranges from
  the ambient to the specular values passed to glMaterial using
  GL_COLOR_INDEXES.  Diffuse and specular coefficients,	computed with a	(.30,
  .59, .11) weighting of the lights' colors, the shininess of the material,
  and the same reflection and attenuation equations as in the RGBA case,
  determine how	much above ambient the resulting index is.

Errors

  GL_INVALID_ENUM is generated if pname	is not an accepted value.

  GL_INVALID_OPERATION is generated if glLightModel is called between a	call
  to glBegin and the corresponding call	to glEnd.

Associated Gets

  glGet	with argument GL_LIGHT_MODEL_AMBIENT
  glGet	with argument GL_LIGHT_MODEL_LOCAL_VIEWER
  glGet	with argument GL_LIGHT_MODEL_TWO_SIDE
  glIsEnabled with argument GL_LIGHTING

See Also

  glLight, glMaterial

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glLineStipple.3gl

Name

  glLineStipple	- specify the line stipple pattern

C Specification

  void glLineStipple( GLint factor,
              GLushort pattern )

Parameters

  factor   Specifies a multiplier for each bit in the line stipple pattern.
       If factor is	3, for example,	each bit in the	pattern	will be	used
       three times before the next bit in the pattern is used.  factor is
       clamped to the range	[1, 256] and defaults to one.

  pattern  Specifies a 16-bit integer whose bit	pattern	determines which
       fragments of	a line will be drawn when the line is rasterized.
       Bit zero is used first, and the default pattern is all ones.

Description

  Line stippling masks out certain fragments produced by rasterization;	those
  fragments will not be	drawn.	The masking is achieved	by using three
  parameters: the 16-bit line stipple pattern pattern, the repeat count
  factor, and an integer stipple counter s.

  Counter s is reset to	zero whenever glBegin is called, and before each line
  segment of a glBegin(GL_LINES)/glEnd sequence	is generated.  It is
  incremented after each fragment of a unit width aliased line segment is
  generated, or	after each i fragments of an i width line segment are
  generated.  The i fragments associated with count s are masked out if

               pattern bit (s /	factor)	mod 16

  is zero, otherwise these fragments are sent to the frame buffer.  Bit	zero
  of pattern is	the least significant bit.

  Antialiased lines are	treated	as a sequence of 1xwidth rectangles for
  purposes of stippling.  Rectangle s is rasterized or not based on the
  fragment rule	described for aliased lines, counting rectangles rather	than
  groups of fragments.

  Line stippling is enabled or disabled	using glEnable and glDisable with
  argument GL_LINE_STIPPLE.  When enabled, the line stipple pattern is
  applied as described above.  When disabled, it is as if the pattern were
  all ones.  Initially,	line stippling is disabled.

Errors

  GL_INVALID_OPERATION is generated if glLineStipple is	executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_LINE_STIPPLE_PATTERN
  glGet	with argument GL_LINE_STIPPLE_REPEAT
  glIsEnabled with argument GL_LINE_STIPPLE

See Also

  glLineWidth, glPolygonStipple

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glLineWidth.3gl

Name

  glLineWidth -	specify	the width of rasterized	lines

C Specification

  void glLineWidth( GLfloat width )

Parameters

  width	 Specifies the width of	rasterized lines.  The default is 1.0.

Description

  glLineWidth specifies	the rasterized width of	both aliased and antialiased
  lines.  Using	a line width other than	1.0 has	different effects, depending
  on whether line antialiasing is enabled.  Line antialiasing is controlled
  by calling glEnable and glDisable with argument GL_LINE_SMOOTH.

  If line antialiasing is disabled, the	actual width is	determined by
  rounding the supplied	width to the nearest integer.  (If the rounding
  results in the value 0, it is	as if the line width were 1.) If |dx| >= |dy|,
  i pixels are filled in each column that is rasterized, where i is the
  rounded value	of width.  Otherwise, i	pixels are filled in each row that is
  rasterized.

  If antialiasing is enabled, line rasterization produces a fragment for each
  pixel	square that intersects the region lying	within the rectangle having
  width	equal to the current line width, length	equal to the actual length of
  the line, and	centered on the	mathematical line segment.  The	coverage
  value	for each fragment is the window	coordinate area	of the intersection
  of the rectangular region with the corresponding pixel square.  This value
  is saved and used in the final rasterization step.

  Not all widths can be	supported when line antialiasing is enabled. If	an
  unsupported width is requested, the nearest supported	width is used.	Only
  width	1.0 is guaranteed to be	supported; others depend on the
  implementation.  The range of	supported widths and the size difference
  between supported widths within the range can	be queried by calling glGet
  with arguments GL_LINE_WIDTH_RANGE and GL_LINE_WIDTH_GRANULARITY.

Notes

  The line width specified by glLineWidth is always returned when
  GL_LINE_WIDTH	is queried.  Clamping and rounding for aliased and
  antialiased lines have no effect on the specified value.

  Non-antialiased line width may be clamped to an implementation-dependent
  maximum.  Although this maximum cannot be queried, it	must be	no less	than
  the maximum value for	antialiased lines, rounded to the nearest integer
  value.

Errors

  GL_INVALID_VALUE is generated	if width is less than or equal to zero.

  GL_INVALID_OPERATION is generated if glLineWidth is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_LINE_WIDTH
  glGet	with argument GL_LINE_WIDTH_RANGE
  glGet	with argument GL_LINE_WIDTH_GRANULARITY
  glIsEnabled with argument GL_LINE_SMOOTH

See Also

  glEnable, glLineSmooth


glListBase.3gl

Name

  glListBase - set the display-list base for glCallLists

C Specification

  void glListBase( GLuint base )

Parameters

  base	Specifies an integer offset that will be added to glCallLists offsets
    to generate display-list names.	 Initial value is zero.

Description

  glCallLists specifies	an array of offsets.  Display-list names are
  generated by adding base to each offset.  Names that reference valid
  display lists	are executed; the others are ignored.

Errors

  GL_INVALID_OPERATION is generated if glListBase is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_LIST_BASE

See Also

  glCallLists

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glLoadIdentity.3gl

Name

  glLoadIdentity - replace the current matrix with the identity	matrix

C Specification

  void glLoadIdentity( void )

Description

  glLoadIdentity replaces the current matrix with the identity matrix.	It is
  semantically equivalent to calling glLoadMatrix with the identity matrix

                  |	1   0	0   0  |
                  |		       |
                  |	0   1	0   0  |
                  |	0   0	1   0  |
                  |		       |
                  |	0   0	0   1  |

  but in some cases it is more efficient.

Errors

  GL_INVALID_OPERATION is generated if glLoadIdentity is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_MATRIX_MODE
  glGet	with argument GL_MODELVIEW_MATRIX
  glGet	with argument GL_PROJECTION_MATRIX
  glGet	with argument GL_TEXTURE_MATRIX

See Also

  glLoadMatrix,	glMatrixMode, glMultMatrix, glPushMatrix

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glLoadMatrix.3gl

Name

  glLoadMatrixd, glLoadMatrixf - replace the current matrix with an arbitrary
  matrix

C Specification

  void glLoadMatrixd( const GLdouble *m	)
  void glLoadMatrixf( const GLfloat *m )

Parameters

  m  Specifies a pointer to a 4x4 matrix stored	in column-major	order as
     sixteen consecutive values.

Description

  glLoadMatrix replaces	the current matrix with	the one	specified in m.	 The
  current matrix is the	projection matrix, modelview matrix, or	texture
  matrix, determined by	the current matrix mode	(see glMatrixMode).

  m points to a	4x4 matrix of single- or double-precision floating-point
  values stored	in column-major	order.	That is, the matrix is stored as
  follows:

                  |a0  a4  a8   a12|
                  |		       |
                  |a1  a5  a9   a13|
                  |		       |
                  |a2  a6  a10  a14|
                  |		       |
                  |a3  a7  a11  a15|
                  |		       |

Errors

  GL_INVALID_OPERATION is generated if glLoadMatrix is called between a	call
  to glBegin and the corresponding call	to glEnd.

Associated Gets

  glGet	with argument GL_MATRIX_MODE
  glGet	with argument GL_MODELVIEW_MATRIX
  glGet	with argument GL_PROJECTION_MATRIX
  glGet	with argument GL_TEXTURE_MATRIX

See Also

  glLoadIdentity, glMatrixMode,	glMultMatrix, glPushMatrix

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glLoadName.3gl

Name

  glLoadName - load a name onto	the name stack

C Specification

  void glLoadName( GLuint name )

Parameters

  name	Specifies a name that will replace the top value on the	name stack.

Description

  The name stack is used during	selection mode to allow	sets of	rendering
  commands to be uniquely identified.  It consists of an ordered set of
  unsigned integers.  glLoadName causes	name to	replace	the value on the top
  of the name stack, which is initially	empty.

  The name stack is always empty while the render mode is not GL_SELECT.
  Calls	to glLoadName while the	render mode is not GL_SELECT are ignored.

Errors

  GL_INVALID_OPERATION is generated if glLoadName is called while the name
  stack	is empty.

  GL_INVALID_OPERATION is generated if glLoadName is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_NAME_STACK_DEPTH
  glGet	with argument GL_MAX_NAME_STACK_DEPTH

See Also

  glInitNames, glPushName, glRenderMode, glSelectBuffer

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glLogicOp.3gl

Name

  glLogicOp - specify a	logical	pixel operation	for color index	rendering

C Specification

  void glLogicOp( GLenum opcode	)

Parameters

  opcode  Specifies a symbolic constant	that selects a logical operation.
      The following	symbols	are accepted: GL_CLEAR,	GL_SET,	GL_COPY,
      GL_COPY_INVERTED, GL_NOOP, GL_INVERT,	GL_AND,	GL_NAND, GL_OR,
      GL_NOR, GL_XOR, GL_EQUIV, GL_AND_REVERSE, GL_AND_INVERTED,
      GL_OR_REVERSE, and GL_OR_INVERTED.

Description

  glLogicOp specifies a	logical	operation that,	when enabled, is applied
  between the incoming color index and the color index at the corresponding
  location in the frame	buffer.	 The logical operation is enabled or disabled
  with glEnable	and glDisable using the	symbolic constant GL_LOGIC_OP.

  opcode is a symbolic constant	chosen from the	list below.  In	the
  explanation of the logical operations, s represents the incoming color
  index	and d represents the index in the frame	buffer.	 Standard C-language
  operators are	used.  As these	bitwise	operators suggest, the logical
  operation is applied independently to	each bit pair of the source and
  destination indices.
             ------------------------------------
             |     opcode      | resulting value |
             ------------------------------------
             |	  GL_CLEAR     |	0	 |
             |	   GL_SET      |	1	 |
             |	  GL_COPY      |	s	 |
             |GL_COPY_INVERTED |       !s	 |
             |	  GL_NOOP      |	d	 |
             |	 GL_INVERT     |       !d	 |
             |	   GL_AND      |      s	& d	 |
             |	  GL_NAND      |    !(s	& d)	 |
             |	   GL_OR       |      s	| d	 |
             |	   GL_NOR      |    !(s	| d)	 |
             |	   GL_XOR      |      s	^ d	 |
             |	  GL_EQUIV     |    !(s	^ d)	 |
             | GL_AND_REVERSE  |     s & !d	 |
             |GL_AND_INVERTED  |     !s	& d	 |
             | GL_OR_REVERSE   |     s | !d	 |
             | GL_OR_INVERTED  |     !s	| d	 |
             ------------------------------------

Notes

  Logical pixel	operations are not applied to RGBA color buffers.

  When more than one color index buffer	is enabled for drawing,	logical
  operations are done separately for each enabled buffer, using	for the
  destination index the	contents of that buffer	(see glDrawBuffer).

  opcode must be one of	the sixteen accepted values.  Other values result in
  an error.

Errors

  GL_INVALID_ENUM is generated if opcode is not	an accepted value.

  GL_INVALID_OPERATION is generated if glLogicOp is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_LOGIC_OP_MODE
  glIsEnabled with argument GL_LOGIC_OP

See Also

  glAlphaFunc, glBlendFunc, glDrawBuffer, glEnable, glStencilOp

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glMap1.3gl

Name

  glMap1d, glMap1f - define a one-dimensional evaluator

C Specification

  void glMap1d(	GLenum target,
        GLdouble u1,
        GLdouble u2,
        GLint stride,
        GLint order,
        const GLdouble *points )
  void glMap1f(	GLenum target,
        GLfloat	u1,
        GLfloat	u2,
        GLint stride,
        GLint order,
        const GLfloat *points )

Parameters

  target  Specifies the	kind of	values that are	generated by the evaluator.
      Symbolic constants GL_MAP1_VERTEX_3, GL_MAP1_VERTEX_4,
      GL_MAP1_INDEX, GL_MAP1_COLOR_4, GL_MAP1_NORMAL,
      GL_MAP1_TEXTURE_COORD_1, GL_MAP1_TEXTURE_COORD_2,
      GL_MAP1_TEXTURE_COORD_3, and GL_MAP1_TEXTURE_COORD_4 are accepted.

  u1, u2  Specify a linear mapping of u, as presented to glEvalCoord1, to u,
      the variable that is evaluated by the	equations specified by this
      command.

  stride  Specifies the	number of floats or doubles between the	beginning of
      one control point and	the beginning of the next one in the data
      structure referenced in points.  This	allows control points to be
      embedded in arbitrary	data structures.  The only constraint is that
      the values for a particular control point must occupy	contiguous
      memory locations.

  order	  Specifies the	number of control points.  Must	be positive.

  points  Specifies a pointer to the array of control points.

Description

  Evaluators provide a way to use polynomial or	rational polynomial mapping
  to produce vertices, normals,	texture	coordinates, and colors.  The values
  produced by an evaluator are sent to further stages of GL processing just
  as if	they had been presented	using glVertex,	glNormal, glTexCoord, and
  glColor commands, except that	the generated values do	not update the
  current normal, texture coordinates, or color.

  All polynomial or rational polynomial	splines	of any degree (up to the
  maximum degree supported by the GL implementation) can be described using
  evaluators.  These include almost all	splines	used in	computer graphics,
  including B-splines, Bezier curves, Hermite splines, and so on.

  Evaluators define curves based on Bernstein polynomials.  Define p(u) as

                 n
                ---
                \   n
               p(u)  =   \ B (u)R
                 /  i    i
                /
                ---
                i=0

                                   n
  where	R  is a	control	point and B (u) is the ith Bernstein polynomial of
         i                         i
  degree n (order = n+1):

                n	     |n|  i      n-i
               B (u)   = | | u  (1-u)
                i	     |i|

  Recall that

                 0	       |n|
                0  = 1 and | |  = 1
                       |0|

  glMap1 is used to define the basis and to specify what kind of values	are
  produced.  Once defined, a map can be	enabled	and disabled by	calling
  glEnable and glDisable with the map name, one	of the nine predefined values
  for target described below.  glEvalCoord1 evaluates the one-dimensional
  maps that are	enabled.  When glEvalCoord1 presents a value u,	the Bernstein
  functions are	evaluated using	u, where

                       u - u1
                  u =  -------
                       u2 - u1

  target is a symbolic constant	that indicates what kind of control points
  are provided in points, and what output is generated when the	map is
  evaluated.  It can assume one	of nine	predefined values:

  GL_MAP1_VERTEX_3	   Each	control	point is three floating-point values
               representing	x, y, and z.  Internal glVertex3
               commands are	generated when the map is evaluated.

  GL_MAP1_VERTEX_4	   Each	control	point is four floating-point values
               representing	x, y, z, and w.	 Internal glVertex4
               commands are	generated when the map is evaluated.

  GL_MAP1_INDEX		   Each	control	point is a single floating-point
               value representing a	color index.  Internal
               glIndex commands are	generated when the map is
               evaluated.  The current index is not	updated	with
               the value of	these glIndex commands,	however.

  GL_MAP1_COLOR_4	   Each	control	point is four floating-point values
               representing	red, green, blue, and alpha.
               Internal glColor4 commands are generated when the
               map is evaluated.  The current color	is not
               updated with	the value of these glColor4 commands,
               however.

  GL_MAP1_NORMAL	   Each	control	point is three floating-point values
               representing	the x, y, and z	components of a
               normal vector.  Internal glNormal commands are
               generated when the map is evaluated.	 The current
               normal is not updated with the value	of these
               glNormal commands, however.

  GL_MAP1_TEXTURE_COORD_1  Each	control	point is a single floating-point
               value representing the s texture coordinate.
               Internal glTexCoord1	commands are generated when
               the map is evaluated.  The current texture
               coordinates are not updated with the	value of
               these glTexCoord commands, however.

  GL_MAP1_TEXTURE_COORD_2  Each	control	point is two floating-point values
               representing	the s and t texture coordinates.
               Internal glTexCoord2	commands are generated when
               the map is evaluated.  The current texture
               coordinates are not updated with the	value of
               these glTexCoord commands, however.

  GL_MAP1_TEXTURE_COORD_3  Each	control	point is three floating-point values
               representing	the s, t, and r	texture	coordinates.
               Internal glTexCoord3	commands are generated when
               the map is evaluated.  The current texture
               coordinates are not updated with the	value of
               these glTexCoord commands, however.

  GL_MAP1_TEXTURE_COORD_4  Each	control	point is four floating-point values
               representing	the s, t, r, and q texture
               coordinates.	 Internal glTexCoord4 commands are
               generated when the map is evaluated.	 The current
               texture coordinates are not updated with the	value
               of these glTexCoord commands, however.

  stride, order, and points define the array addressing	for accessing the
  control points.  points is the location of the first control point, which
  occupies one,	two, three, or four contiguous memory locations, depending on
  which	map is being defined.  order is	the number of control points in	the
  array.  stride tells how many	float or double	locations to advance the
  internal memory pointer to reach the next control point.

Notes

  As is	the case with all GL commands that accept pointers to data, it is as
  if the contents of points were copied	by glMap1 before it returned.
  Changes to the contents of points have no effect after glMap1	is called.

Errors

  GL_INVALID_ENUM is generated if target is not	an accepted value.

  GL_INVALID_VALUE is generated	if u1 is equal to u2.

  GL_INVALID_VALUE is generated	if stride is less than the number of values
  in a control point.

  GL_INVALID_VALUE is generated	if order is less than one or greater than
  GL_MAX_EVAL_ORDER.

  GL_INVALID_OPERATION is generated if glMap1 is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGetMap
  glGet	with argument GL_MAX_EVAL_ORDER
  glIsEnabled with argument GL_MAP1_VERTEX_3
  glIsEnabled with argument GL_MAP1_VERTEX_4
  glIsEnabled with argument GL_MAP1_INDEX
  glIsEnabled with argument GL_MAP1_COLOR_4
  glIsEnabled with argument GL_MAP1_NORMAL
  glIsEnabled with argument GL_MAP1_TEXTURE_COORD_1
  glIsEnabled with argument GL_MAP1_TEXTURE_COORD_2
  glIsEnabled with argument GL_MAP1_TEXTURE_COORD_3
  glIsEnabled with argument GL_MAP1_TEXTURE_COORD_4

See Also

  glBegin, glColor, glEnable, glEvalCoord, glEvalMesh, glEvalPoint, glMap2,
  glMapGrid, glNormal, glTexCoord, glVertex

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glMap2.3gl

Name

  glMap2d, glMap2f - define a two-dimensional evaluator

C Specification

  void glMap2d(	GLenum target,
        GLdouble u1,
        GLdouble u2,
        GLint ustride,
        GLint uorder,
        GLdouble v1,
        GLdouble v2,
        GLint vstride,
        GLint vorder,
        const GLdouble *points )
  void glMap2f(	GLenum target,
        GLfloat	u1,
        GLfloat	u2,
        GLint ustride,
        GLint uorder,
        GLfloat	v1,
        GLfloat	v2,
        GLint vstride,
        GLint vorder,
        const GLfloat *points )

Parameters

  target   Specifies the kind of values	that are generated by the evaluator.
       Symbolic constants GL_MAP2_VERTEX_3,	GL_MAP2_VERTEX_4,
       GL_MAP2_INDEX, GL_MAP2_COLOR_4, GL_MAP2_NORMAL,
       GL_MAP2_TEXTURE_COORD_1, GL_MAP2_TEXTURE_COORD_2,
       GL_MAP2_TEXTURE_COORD_3, and	GL_MAP2_TEXTURE_COORD_4	are accepted.

  u1, u2   Specify a linear mapping of u, as presented to glEvalCoord2, to u,
       one of the two variables that is evaluated by the equations
       specified by	this command.

  ustride  Specifies the number	of floats or doubles between the beginning of
       control point R   and the beginning of control point	R        ,
                          ij                                     (i+1) j
       where i and j are the u and v control point indices,	respectively.
       This	allows control points to be embedded in	arbitrary data
       structures.	The only constraint is that the	values for a
       particular control point must occupy	contiguous memory locations.

  uorder   Specifies the dimension of the control point	array in the u axis.
       Must	be positive.

  v1, v2   Specify a linear mapping of v, as presented to glEvalCoord2,	to v,
       one of the two variables that is evaluated by the equations
       specified by	this command.

  vstride  Specifies the number	of floats or doubles between the beginning of
       control point R   and the beginning of control point	R        ,
              ij                                     (i+1) j
       where i and j are the u and v control point indices,	respectively.
       This	allows control points to be embedded in	arbitrary data
       structures.	The only constraint is that the	values for a
       particular control point must occupy	contiguous memory locations.

  vorder   Specifies the dimension of the control point	array in the v axis.
       Must	be positive.

  points   Specifies a pointer to the array of control points.

Description

  Evaluators provide a way to use polynomial or	rational polynomial mapping
  to produce vertices, normals,	texture	coordinates, and colors.  The values
  produced by an evaluator are sent on to further stages of GL processing
  just as if they had been presented using glVertex, glNormal, glTexCoord,
  and glColor commands, except that the generated values do not	update the
  current normal, texture coordinates, or color.

  All polynomial or rational polynomial	splines	of any degree (up to the
  maximum degree supported by the GL implementation) can be described using
  evaluators.  These include almost all	surfaces used in computer graphics,
  including B-spline surfaces, NURBS surfaces, Bezier surfaces,	and so on.

  Evaluators define surfaces based on bivariate	Bernstein polynomials.
  Define p(u,v) as

                    n   m
           --- ---
              \   \   n    m
        p(u,v)  =   \   \ B (u)B (v) R
            /   /  i    j     ij
           /   /
           --- ---
           i=0 j=0

                                 n
  where	R   is a control point,	B (u) is the ith Bernstein polynomial of
         ij                      i
  degree n (uorder = n+1)

                n	     |n| i     n-i
               B (u)  =  | |u (1-u)
                i	     |i|

       m
  and B (v') is the jth Bernstein polynomial of degree m (vorder = m+1)
       j
                m	     |m| j     m-j
               B (v)  =  | |v (1-v)
                j	     |j|

  Recall that

                 0	       |n|
                0  = 1 and | | = 1
                       |0|

  glMap2 is used to define the basis and to specify what kind of values	are
  produced.  Once defined, a map can be	enabled	and disabled by	calling
  glEnable and glDisable with the map name, one	of the nine predefined values
  for target, described	below.	When glEvalCoord2 presents values u and	v,
  the bivariate Bernstein polynomials are evaluated using u and v, where

                    u - u1
                 u  =  -------
                        u2 - u1

                    v - v1
                 v  =  -------
                        v2 - v1

  target is a symbolic constant	that indicates what kind of control points
  are provided in points, and what output is generated when the	map is
  evaluated.  It can assume one	of nine	predefined values:

  GL_MAP2_VERTEX_3	   Each	control	point is three floating-point values
               representing	x, y, and z.  Internal glVertex3
               commands are	generated when the map is evaluated.

  GL_MAP2_VERTEX_4	   Each	control	point is four floating-point values
               representing	x, y, z, and w.	 Internal glVertex4
               commands are	generated when the map is evaluated.

  GL_MAP2_INDEX		   Each	control	point is a single floating-point
               value representing a	color index.  Internal
               glIndex commands are	generated when the map is
               evaluated.  The current index is not	updated	with
               the value of	these glIndex commands,	however.

  GL_MAP2_COLOR_4	   Each	control	point is four floating-point values
               representing	red, green, blue, and alpha.
               Internal glColor4 commands are generated when the
               map is evaluated.  The current color	is not
               updated with	the value of these glColor4 commands,
               however.

  GL_MAP2_NORMAL	   Each	control	point is three floating-point values
               representing	the x, y, and z	components of a
               normal vector.  Internal glNormal commands are
               generated when the map is evaluated.	 The current
               normal is not updated with the value	of these
               glNormal commands, however.

  GL_MAP2_TEXTURE_COORD_1  Each	control	point is a single floating-point
               value representing the s texture coordinate.
               Internal glTexCoord1	commands are generated when
               the map is evaluated.  The current texture
               coordinates are not updated with the	value of
               these glTexCoord commands, however.

  GL_MAP2_TEXTURE_COORD_2  Each	control	point is two floating-point values
               representing	the s and t texture coordinates.
               Internal glTexCoord2	commands are generated when
               the map is evaluated.  The current texture
               coordinates are not updated with the	value of
               these glTexCoord commands, however.

  GL_MAP2_TEXTURE_COORD_3  Each	control	point is three floating-point values
               representing	the s, t, and r	texture	coordinates.
               Internal glTexCoord3	commands are generated when
               the map is evaluated.  The current texture
               coordinates are not updated with the	value of
               these glTexCoord commands, however.

  GL_MAP2_TEXTURE_COORD_4  Each	control	point is four floating-point values
               representing	the s, t, r, and q texture
               coordinates.	 Internal glTexCoord4 commands are
               generated when the map is evaluated.	 The current
               texture coordinates are not updated with the	value
               of these glTexCoord commands, however.

  ustride, uorder, vstride, vorder, and	points define the array	addressing
  for accessing	the control points.  points is the location of the first
  control point, which occupies	one, two, three, or four contiguous memory
  locations, depending on which	map is being defined.  There are
  uorderxvorder	control	points in the array.  ustride tells how	many float or
  double locations are skipped to advance the internal memory pointer from
  control point	R   to control point R	    .  vstride tells how many float
                 ij                   (i+1)j
  or double locations are skipped to advance the internal memory pointer from
  control point	R   to control point R	    .
         ij		      i(j+1)

Notes

  As is	the case with all GL commands that accept pointers to data, it is as
  if the contents of points were copied	by glMap2 before it returned.
  Changes to the contents of points have no effect after glMap2	is called.

Errors

  GL_INVALID_ENUM is generated if target is not	an accepted value.

  GL_INVALID_VALUE is generated	if u1 is equal to u2, or if v1 is equal	to
  v2.

  GL_INVALID_VALUE is generated	if either ustride or vstride is	less than the
  number of values in a	control	point.

  GL_INVALID_VALUE is generated	if either uorder or vorder is less than	one
  or greater than GL_MAX_EVAL_ORDER.

  GL_INVALID_OPERATION is generated if glMap2 is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGetMap
  glGet	with argument GL_MAX_EVAL_ORDER
  glIsEnabled with argument GL_MAP2_VERTEX_3
  glIsEnabled with argument GL_MAP2_VERTEX_4
  glIsEnabled with argument GL_MAP2_INDEX
  glIsEnabled with argument GL_MAP2_COLOR_4
  glIsEnabled with argument GL_MAP2_NORMAL
  glIsEnabled with argument GL_MAP2_TEXTURE_COORD_1
  glIsEnabled with argument GL_MAP2_TEXTURE_COORD_2
  glIsEnabled with argument GL_MAP2_TEXTURE_COORD_3
  glIsEnabled with argument GL_MAP2_TEXTURE_COORD_4

See Also

  glBegin, glColor, glEnable, glEvalCoord, glEvalMesh, glEvalPoint, glMap1,
  glMapGrid, glNormal, glTexCoord, glVertex

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glMapGrid.3gl

Name

  glMapGrid1d, glMapGrid1f, glMapGrid2d, glMapGrid2f - define a	one- or	two-
  dimensional mesh

C Specification

  void glMapGrid1d( GLint un,
            GLdouble u1,
            GLdouble u2	)
  void glMapGrid1f( GLint un,
            GLfloat u1,
            GLfloat u2 )
  void glMapGrid2d( GLint un,
            GLdouble u1,
            GLdouble u2,
            GLint vn,
            GLdouble v1,
            GLdouble v2	)
  void glMapGrid2f( GLint un,
            GLfloat u1,
            GLfloat u2,
            GLint vn,
            GLfloat v1,
            GLfloat v2 )

Parameters

  un  Specifies	the number of partitions in the	grid range interval [u1, u2].
      Must be positive.

  u1, u2
      Specify the mappings for integer grid domain values i=0 and i=un.

  vn  Specifies	the number of partitions in the	grid range interval [v1, v2]
      (glMapGrid2 only).

  v1, v2
      Specify the mappings for integer grid domain values j=0 and j=vn
      (glMapGrid2 only).

Description

  glMapGrid and	glEvalMesh are used in tandem to efficiently generate and
  evaluate a series of evenly spaced map domain	values.	 glEvalMesh steps
  through the integer domain of	a one- or two-dimensional grid,	whose range
  is the domain	of the evaluation maps specified by glMap1 and glMap2.

  glMapGrid1 and glMapGrid2 specify the	linear grid mappings between the i
  (or i	and j) integer grid coordinates, to the	u (or u	and v) floating-point
  evaluation map coordinates.  See glMap1 and glMap2 for details of how	u and
  v coordinates	are evaluated.

  glMapGrid1 specifies a single	linear mapping such that integer grid
  coordinate 0 maps exactly to u1, and integer grid coordinate un maps
  exactly to u2.  All other integer grid coordinates i are mapped such that

                 u = i(u2-u1)/un + u1

  glMapGrid2 specifies two such	linear mappings.  One maps integer grid
  coordinate i=0 exactly to u1,	and integer grid coordinate i=un exactly to
  u2.  The other maps integer grid coordinate j=0 exactly to v1, and integer
  grid coordinate j=vn exactly to v2.  Other integer grid coordinates i	and j
  are mapped such that

                 u = i(u2-u1)/un + u1

                 v = j(v2-v1)/vn + v1

  The mappings specified by glMapGrid are used identically by glEvalMesh and
  glEvalPoint.

Errors

  GL_INVALID_VALUE is generated	if either un or	vn is not positive.

  GL_INVALID_OPERATION is generated if glMapGrid is called between a call to
  glBegin and the corresponding	call to	glEnd.

Associated Gets

  glGet	with argument GL_MAP1_GRID_DOMAIN
  glGet	with argument GL_MAP2_GRID_DOMAIN
  glGet	with argument GL_MAP1_GRID_SEGMENTS
  glGet	with argument GL_MAP2_GRID_SEGMENTS

See Also

  glEvalCoord, glEvalMesh, glEvalPoint,	glMap1,	glMap2

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glMaterial.3gl

Name

  glMaterialf, glMateriali, glMaterialfv, glMaterialiv - specify material
  parameters for the lighting model

C Specification

  void glMaterialf( GLenum face,
            GLenum pname,
            GLfloat param )
  void glMateriali( GLenum face,
            GLenum pname,
            GLint param	)

Parameters

  face	  Specifies which face or faces	are being updated.  Must be one	of
      GL_FRONT, GL_BACK, or	GL_FRONT_AND_BACK.

  pname	  Specifies the	single-valued material parameter of the	face or	faces
      that is being	updated.  Must be GL_SHININESS.

  param	  Specifies the	value that parameter GL_SHININESS will be set to.

C Specification

  void glMaterialfv( GLenum face,
             GLenum pname,
             const GLfloat *params )
  void glMaterialiv( GLenum face,
             GLenum pname,
             const GLint *params )

Parameters

  face Specifies which face or faces are being updated.	 Must be one of
       GL_FRONT, GL_BACK, or GL_FRONT_AND_BACK.

  pname
       Specifies the material parameter	of the face or faces that is being
       updated.	 Must be one of	GL_AMBIENT, GL_DIFFUSE,	GL_SPECULAR,
       GL_EMISSION, GL_SHININESS, GL_AMBIENT_AND_DIFFUSE,  or
       GL_COLOR_INDEXES.

  params
       Specifies a pointer to the value	or values that pname will be set to.

Description

  glMaterial assigns values to material	parameters.  There are two matched
  sets of material parameters.	One, the front-facing set, is used to shade
  points, lines, bitmaps, and all polygons (when two-sided lighting is
  disabled), or	just front-facing polygons (when two-sided lighting is
  enabled).  The other set, back-facing, is used to shade back-facing
  polygons only	when two-sided lighting	is enabled.  Refer to the
  glLightModel reference page for details concerning one- and two-sided
  lighting calculations.

  glMaterial takes three arguments.  The first,	face, specifies	whether	the
  GL_FRONT materials, the GL_BACK materials, or	both GL_FRONT_AND_BACK
  materials will be modified.  The second, pname, specifies which of several
  parameters in	one or both sets will be modified.  The	third, params,
  specifies what value or values will be assigned to the specified parameter.

  Material parameters are used in the lighting equation	that is	optionally
  applied to each vertex.  The equation	is discussed in	the glLightModel
  reference page.  The parameters that can be specified	using glMaterial, and
  their	interpretations	by the lighting	equation, are as follows:

  GL_AMBIENT	      params contains four integer or floating-point values
              that specify the ambient RGBA reflectance	of the
              material.	 Integer values	are mapped linearly such that
              the most positive	representable value maps to 1.0, and
              the most negative	representable value maps to -1.0.
              Floating-point values are	mapped directly.  Neither
              integer nor floating-point values	are clamped.  The
              default ambient reflectance for both front- and back-
              facing materials is (0.2,	0.2, 0.2, 1.0).

  GL_DIFFUSE	      params contains four integer or floating-point values
              that specify the diffuse RGBA reflectance	of the
              material.	 Integer values	are mapped linearly such that
              the most positive	representable value maps to 1.0, and
              the most negative	representable value maps to -1.0.
              Floating-point values are	mapped directly.  Neither
              integer nor floating-point values	are clamped.  The
              default diffuse reflectance for both front- and back-
              facing materials is (0.8,	0.8, 0.8, 1.0).

  GL_SPECULAR	      params contains four integer or floating-point values
              that specify the specular	RGBA reflectance of the
              material.	 Integer values	are mapped linearly such that
              the most positive	representable value maps to 1.0, and
              the most negative	representable value maps to -1.0.
              Floating-point values are	mapped directly.  Neither
              integer nor floating-point values	are clamped.  The
              default specular reflectance for both front- and back-
              facing materials is (0.0,	0.0, 0.0, 1.0).

  GL_EMISSION	      params contains four integer or floating-point values
              that specify the RGBA emitted light intensity of the
              material.	 Integer values	are mapped linearly such that
              the most positive	representable value maps to 1.0, and
              the most negative	representable value maps to -1.0.
              Floating-point values are	mapped directly.  Neither
              integer nor floating-point values	are clamped.  The
              default emission intensity for both front- and back-
              facing materials is (0.0,	0.0, 0.0, 1.0).

  GL_SHININESS	      params is	a single integer or floating-point value that
              specifies	the RGBA specular exponent of the material.
              Integer and floating-point values	are mapped directly.
              Only values in the range [0,128] are accepted.  The
              default specular exponent	for both front-	and back-
              facing materials is 0.

  GL_AMBIENT_AND_DIFFUSE
              Equivalent to calling glMaterial twice with the same
              parameter	values,	once with GL_AMBIENT and once with
              GL_DIFFUSE.

  GL_COLOR_INDEXES    params contains three integer or floating-point values
              specifying the color indices for ambient,	diffuse, and
              specular lighting.  These	three values, and
              GL_SHININESS, are	the only material values used by the
              color index mode lighting	equation.  Refer to the
              glLightModel reference page for a	discussion of color
              index lighting.

Notes

  The material parameters can be updated at any	time.  In particular,
  glMaterial can be called between a call to glBegin and the corresponding
  call to glEnd.  If only a single material parameter is to be changed per
  vertex, however, glColorMaterial is preferred	over glMaterial	(see
  glColorMaterial).

Errors

  GL_INVALID_ENUM is generated if either face or pname is not an accepted
  value.

  GL_INVALID_VALUE is generated	if a specular exponent outside the range
  [0,128] is specified.

Associated Gets

  glGetMaterial

See Also

  glColorMaterial, glLight, glLightModel

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glMatrixMode.3gl

Name

  glMatrixMode - specify which matrix is the current matrix

C Specification

  void glMatrixMode( GLenum mode )

Parameters

  mode	Specifies which	matrix stack is	the target for subsequent matrix
    operations.  Three values are accepted:	GL_MODELVIEW, GL_PROJECTION,
    and GL_TEXTURE.	 The default value is GL_MODELVIEW.

Description

  glMatrixMode sets the	current	matrix mode.  mode can assume one of three
  values:

  GL_MODELVIEW	      Applies subsequent matrix	operations to the modelview
              matrix stack.

  GL_PROJECTION	      Applies subsequent matrix	operations to the projection
              matrix stack.

  GL_TEXTURE	      Applies subsequent matrix	operations to the texture
              matrix stack.

Errors

  GL_INVALID_ENUM is generated if mode is not an accepted value.

  GL_INVALID_OPERATION is generated if glMatrixMode is executed	between	the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_MATRIX_MODE

See Also

  glLoadMatrix,	glPushMatrix

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glMultMatrix.3gl

Name

  glMultMatrixd, glMultMatrixf - multiply the current matrix by	an arbitrary
  matrix

C Specification

  void glMultMatrixd( const GLdouble *m	)
  void glMultMatrixf( const GLfloat *m )

Parameters

  m  Specifies a pointer a to 4x4 matrix stored	in column-major	order as
     sixteen consecutive values.

Description

  glMultMatrix multiplies the current matrix with the one specified in m.
  That is, if M	is the current matrix and T is the matrix passed to
  glMultMatrix,	then M is replaced with	M · T.

  The current matrix is	the projection matrix, modelview matrix, or texture
  matrix, determined by	the current matrix mode	(see glMatrixMode).

  m points to a	4x4 matrix of single- or double-precision floating-point
  values stored	in column-major	order.	That is, the matrix is stored as

                  |a0  a4  a8   a12|
                  |		       |
                  |a1  a5  a9   a13|
                  |		       |
                  |a2  a6  a10  a14|
                  |		       |
                  |a3  a7  a11  a15|

Errors

  GL_INVALID_OPERATION is generated if glMultMatrix is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_MATRIX_MODE
  glGet	with argument GL_MODELVIEW_MATRIX
  glGet	with argument GL_PROJECTION_MATRIX
  glGet	with argument GL_TEXTURE_MATRIX

See Also

  glMatrixMode,	glLoadIdentity,	glLoadMatrix, glPushMatrix

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glNewList.3gl

Name

  glNewList, glEndList - create	or replace a display list

C Specification

  void glNewList( GLuint list,
          GLenum mode )

Parameters

  list	Specifies the display list name.

  mode	Specifies the compilation mode,	which can be GL_COMPILE	or
    GL_COMPILE_AND_EXECUTE.

C Specification

  void glEndList( void )

Description

  Display lists	are groups of GL commands that have been stored	for
  subsequent execution.	 The display lists are created with glNewList.	All
  subsequent commands are placed in the	display	list, in the order issued,
  until	glEndList is called.

  glNewList has	two arguments.	The first argument, list, is a positive
  integer that becomes the unique name for the display list.  Names can	be
  created and reserved with glGenLists and tested for uniqueness with
  glIsList.  The second	argument, mode,	is a symbolic constant that can
  assume one of	two values:

  GL_COMPILE	      Commands are merely compiled.

  GL_COMPILE_AND_EXECUTE
              Commands are executed as they are	compiled into the
              display list.

  Certain commands are not compiled into the display list, but are executed
  immediately, regardless of the display-list mode.  These commands are
  glIsList, glGenLists,	glDeleteLists, glFeedbackBuffer, glSelectBuffer,
  glRenderMode,	glReadPixels, glPixelStore, glFlush, glFinish, glIsEnabled,
  and all of the glGet routines.

  When glEndList is encountered, the display-list definition is	completed by
  associating the list with the	unique name list (specified in the glNewList
  command). If a display list with name	list already exists, it	is replaced
  only when glEndList is called.

Notes

  glCallList and glCallLists can be entered into display lists.	 The commands
  in the display list or lists executed	by glCallList or glCallLists are not
  included in the display list being created, even if the list creation	mode
  is GL_COMPILE_AND_EXECUTE.

  A display list is just a group of commands and arguments, so errors
  generated by commands	in a display list must be generated when the list is
  executed. If the list	is created in GL_COMPILE mode, errors are not
  generated until the list is executed.

Errors

  GL_INVALID_VALUE is generated	if list	is zero.

  GL_INVALID_ENUM is generated if mode is not an accepted value.

  GL_INVALID_OPERATION is generated if glEndList is called without a
  preceding glNewList, or if glNewList is called while a display list is
  being	defined.

  GL_INVALID_OPERATION is generated if glNewList or glEndList is executed
  between the execution	of glBegin and the corresponding execution of glEnd.

  GL_OUT_OF_MEMORY is generated	if there is insufficient memory	to compile
  the display list.

Associated Gets

  glIsList

See Also

  glCallList, glCallLists, glDeleteLists, glGenLists

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glNormal.3gl

Name

  glNormal3b, glNormal3d, glNormal3f, glNormal3i, glNormal3s, glNormal3bv,
  glNormal3dv, glNormal3fv, glNormal3iv, glNormal3sv - set the current normal
  vector

C Specification

  void glNormal3b( GLbyte nx,
           GLbyte ny,
           GLbyte nz )
  void glNormal3d( GLdouble nx,
           GLdouble ny,
           GLdouble nz )
  void glNormal3f( GLfloat nx,
           GLfloat ny,
           GLfloat nz )
  void glNormal3i( GLint nx,
           GLint ny,
           GLint nz )
  void glNormal3s( GLshort nx,
           GLshort ny,
           GLshort nz )

Parameters

  nx, ny, nz
        Specify the	x, y, and z coordinates	of the new current normal.
        The	initial	value of the current normal is (0,0,1).

C Specification

  void glNormal3bv( const GLbyte *v )
  void glNormal3dv( const GLdouble *v )
  void glNormal3fv( const GLfloat *v )
  void glNormal3iv( const GLint	*v )
  void glNormal3sv( const GLshort *v )

Parameters

  v    Specifies a pointer to an array of three	elements: the x, y, and	z
       coordinates of the new current normal.

Description

  The current normal is	set to the given coordinates whenever glNormal is
  issued.  Byte, short,	or integer arguments are converted to floating-point
  format with a	linear mapping that maps the most positive representable
  integer value	to 1.0,	and the	most negative representable integer value to
  -1.0.

  Normals specified with glNormal need not have	unit length.  If
  normalization	is enabled, then normals specified with	glNormal are
  normalized after transformation.  Normalization is controlled	using
  glEnable and glDisable with the argument GL_NORMALIZE.  By default,
  normalization	is disabled.

Notes

  The current normal can be updated at any time.  In particular, glNormal can
  be called between a call to glBegin and the corresponding call to glEnd.

Associated Gets

  glGet	with argument GL_CURRENT_NORMAL
  glIsEnable with argument GL_NORMALIZE

See Also

  glBegin, glColor, glIndex, glTexCoord, glVertex

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glNormalPointerEXT.3gl

Name

  glNormalPointerEXT - define a	array of normals

C Specification

  void glNormalPointerEXT( GLenum type,
               GLsizei stride,
               GLsizei count,
               const GLvoid	*pointer )

Parameters

  type	   Specifies the the data type of each coordinate in the array.
       Symbolic constants GL_BYTE, GL_SHORT, GL_INT, GL_FLOAT, or
       GL_DOUBLE_EXT are accepted.

  stride   Specifies the byte offset between consecutive normals.

  count	   Specifies the number	of normals, counting from the first, that are
       static.

  pointer  Specifies a pointer to the first coordinate of the first normal in
       the array.

Description

  glNormalPointerEXT specifies the location and data format of an array of
  normals to use when rendering using the vertex array extension. type specifies
  the data type of the normal coordinates and stride gives the byte stride
  from one normal to the next allowing vertexes and attributes to be packed
  into a single array or stored in separate arrays.  (Single-array storage may
  be more efficient on some implementations.) count indicates the number of
  array elements (counting from the first) that are static. Static elements
  may be modified by the application, but once they are modified, the
  application must explicitly respecify the array before using it for any
  rendering. When a normal array is specified, type, stride, count and pointer
  are saved as client-side state, and static array elements may be cached by
  the implementation.

  The normal array is enabled and disabled using glEnable and glDisable	with
  the argument GL_NORMAL_ARRAY_EXT. If enabled,	the normal array is used when
  glDrawArraysEXT or glArrayElementEXT is called.

  Use glDrawArraysEXT to define	a sequence of primitives (all of the same
  type)	from pre-specified vertex and vertex attribute arrays.	Use
  glArrayElementEXT to specify primitives by indexing vertexes and vertex
  attributes.

Notes

  Non-static array elements are	not accessed until glArrayElementEXT or
  glDrawArraysEXT is executed.

  By default the normal	array is disabled and it won't be accessed when
  glArrayElementEXT or glDrawArraysEXT is called.

  Although it is not an error to call glNormalPointerEXT between the
  execution of glBegin and the corresponding execution of glEnd, the results
  are undefined.

  glNormalPointerEXT will typically be implemented on the client side with no
  protocol.

  Since	the normal array parameters are	client side state, they	are not	saved
  or restored by glPushAttrib and glPopAttrib.

  glNormalPointerEXT commands are not entered into display lists.

  glNormalPointerEXT is	part of	the EXT_vertex_array extension, not
  part of the core GL command set. If "GL_EXT_vertex_array" is
  included in the string returned by glGetString, when called with argument
  GL_EXTENSIONS, extension EXT_vertex_array is supported.

Errors

  GL_INVALID_ENUM is generated if type is not an accepted value.

  GL_INVALID_VALUE is generated	if stride or count is negative.

Associated Gets

  glIsEnabled with argument GL_NORMAL_ARRAY_EXT
  glGet	with argument GL_NORMAL_ARRAY_TYPE_EXT
  glGet	with argument GL_NORMAL_ARRAY_STRIDE_EXT
  glGet	with argument GL_NORMAL_ARRAY_COUNT_EXT
  glGetPointervEXT with	argument GL_NORMAL_ARRAY_POINTER_EXT

See Also

  glArrayElementEXT, glColorPointerEXT,	glDrawArraysEXT,
  glEdgeFlagPointerEXT,	glGetPointervEXT, glIndexPointerEXT,
  glTexCoordPointerEXT,	glVertexPointerEXT, glEnable

Introduction | Alphabetic | Specification

Last Edited: Fri Feb 24, 1995

AFV


glOrtho.3gl

Name

  glOrtho - multiply the current matrix	by an orthographic matrix

C Specification

  void glOrtho(	GLdouble left,
        GLdouble right,
        GLdouble bottom,
        GLdouble top,
        GLdouble near,
        GLdouble far )

Parameters

  left,	right Specify the coordinates for the left and right vertical
          clipping planes.

  bottom, top Specify the coordinates for the bottom and top horizontal
          clipping planes.

  near,	far   Specify the distances to the nearer and farther depth clipping
          planes.  These distances are negative if the plane is to be
          behind the viewer.

Description

  glOrtho describes a perspective matrix that produces a parallel projection.
  (left, bottom,  -near) and (right, top,  -near) specify the points on	the
  near clipping	plane that are mapped to the lower left	and upper right
  corners of the window, respectively, assuming	that the eye is	located	at
  (0, 0, 0).  -far specifies the location of the far clipping plane.  Both
  near and far can be either positive or negative.  The	corresponding matrix
  is

            |     2	     		          |
            |----------      0          0      t  |
            |right-left				x |
            |					  |
            |		      2    		  |
            |    0       ----------	0      t  |
            |	  	 top-bottom	        y |
            |                                     |
            |					  |
            |	 0	     0	        -2        |
            |                        --------  t  |
            |			     far-near	z |
            |                                     |
            |	 0	     0		0      1  |

  where
                          right+left
                   t  = - ----------
                x     right-left

                      top+bottom
                   t  = - ----------
                y     top-bottom

                       far+near
                t  = - --------
                 z     far-near

  The current matrix is	multiplied by this matrix with the result replacing
  the current matrix.  That is,	if M is	the current matrix and O is the	ortho
  matrix, then M is replaced with M · O.

  Use glPushMatrix and glPopMatrix to save and restore the current matrix
  stack.

Errors

  GL_INVALID_OPERATION is generated if glOrtho is executed between the
  execution of	glBegin	and the	corresponding execution	of glEnd.

Associated Gets

  glGet	with argument GL_MATRIX_MODE
  glGet	with argument GL_MODELVIEW_MATRIX
  glGet	with argument GL_PROJECTION_MATRIX
  glGet	with argument GL_TEXTURE_MATRIX

See Also

  glFrustum, glMatrixMode, glMultMatrix, glPushMatrix, glViewport

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glPassThrough.3gl

Name

  glPassThrough	- place	a marker in the	feedback buffer

C Specification

  void glPassThrough( GLfloat token )

Parameters

  token	 Specifies a marker value to be	placed in the feedback buffer
     following a GL_PASS_THROUGH_TOKEN.

Description

  Feedback is a	GL render mode.	 The mode is selected by calling glRenderMode
  with GL_FEEDBACK.  When the GL is in feedback	mode, no pixels	are produced
  by rasterization.  Instead, information about	primitives that	would have
  been rasterized is fed back to the application using the GL.	See
  glFeedbackBuffer for a description of	the feedback buffer and	the values in
  it.

  glPassThrough	inserts	a user-defined marker in the feedback buffer when it
  is executed in feedback mode.	token is returned as if	it were	a primitive;
  it is	indicated with its own unique identifying value:
  GL_PASS_THROUGH_TOKEN.  The order of glPassThrough commands with respect to
  the specification of graphics	primitives is maintained.

Notes

  glPassThrough	is ignored if the GL is	not in feedback	mode.

Errors

  GL_INVALID_OPERATION is generated if glPassThrough is	executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_RENDER_MODE

See Also

  glFeedbackBuffer, glRenderMode

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glPixelMap.3gl

Name

  glPixelMapfv,	glPixelMapuiv, glPixelMapusv - set up pixel transfer maps

C Specification

  void glPixelMapfv( GLenum map,
             GLint mapsize,
             const GLfloat *values )
  void glPixelMapuiv( GLenum map,
              GLint mapsize,
              const GLuint *values )
  void glPixelMapusv( GLenum map,
              GLint mapsize,
              const GLushort *values )

Parameters

  map	   Specifies a symbolic	map name.  Must	be one of the following:
       GL_PIXEL_MAP_I_TO_I,	GL_PIXEL_MAP_S_TO_S, GL_PIXEL_MAP_I_TO_R,
       GL_PIXEL_MAP_I_TO_G,	GL_PIXEL_MAP_I_TO_B, GL_PIXEL_MAP_I_TO_A,
       GL_PIXEL_MAP_R_TO_R,	GL_PIXEL_MAP_G_TO_G, GL_PIXEL_MAP_B_TO_B, or
       GL_PIXEL_MAP_A_TO_A.

  mapsize  Specifies the size of the map being defined.

  values   Specifies an	array of mapsize values.

Description

  glPixelMap sets up translation tables, or maps, used by glDrawPixels,
  glReadPixels,	glCopyPixels, glTexImage1D, and	glTexImage2D.  Use of these
  maps is described completely in the glPixelTransfer reference	page, and
  partly in the	reference pages	for the	pixel and texture image	commands.
  Only the specification of the	maps is	described in this reference page.

  map is a symbolic map	name, indicating one of	ten maps to set.  mapsize
  specifies the	number of entries in the map, and values is a pointer to an
  array	of mapsize map values.

  The ten maps are as follows:

  GL_PIXEL_MAP_I_TO_I		Maps color indices to color indices.

  GL_PIXEL_MAP_S_TO_S		Maps stencil indices to	stencil	indices.

  GL_PIXEL_MAP_I_TO_R		Maps color indices to red components.

  GL_PIXEL_MAP_I_TO_G		Maps color indices to green components.

  GL_PIXEL_MAP_I_TO_B		Maps color indices to blue components.

  GL_PIXEL_MAP_I_TO_A		Maps color indices to alpha components.

  GL_PIXEL_MAP_R_TO_R		Maps red components to red components.

  GL_PIXEL_MAP_G_TO_G		Maps green components to green components.

  GL_PIXEL_MAP_B_TO_B		Maps blue components to	blue components.

  GL_PIXEL_MAP_A_TO_A		Maps alpha components to alpha components.

  The entries in a map can be specified	as single-precision floating-point
  numbers, unsigned short integers, or unsigned	long integers.	Maps that
  store	color component	values (all but	GL_PIXEL_MAP_I_TO_I and
  GL_PIXEL_MAP_S_TO_S) retain their values in floating-point format, with
  unspecified mantissa and exponent sizes.  Floating-point values specified
  by glPixelMapfv are converted	directly to the	internal floating-point
  format of these maps,	then clamped to	the range [0,1].  Unsigned integer
  values specified by glPixelMapusv and	glPixelMapuiv are converted linearly
  such that the	largest	representable integer maps to 1.0, and zero maps to
  0.0.

  Maps that store indices, GL_PIXEL_MAP_I_TO_I and GL_PIXEL_MAP_S_TO_S,
  retain their values in fixed-point format, with an unspecified number	of
  bits to the right of the binary point.  Floating-point values	specified by
  glPixelMapfv are converted directly to the internal fixed-point format of
  these	maps.  Unsigned	integer	values specified by glPixelMapusv and
  glPixelMapuiv	specify	integer	values,	with all zeros to the right of the
  binary point.

  The table below shows	the initial sizes and values for each of the maps.
  Maps that are	indexed	by either color	or stencil indices must	have mapsize
  = 2n for some	n or results are undefined.  The maximum allowable size	for
  each map depends on the implementation and can be determined by calling
  glGet	with argument GL_MAX_PIXEL_MAP_TABLE.  The single maximum applies to
  all maps, and	it is at least 32.
--------------------------------------------------------------------------------------
|        map         |  lookup index  |  lookup value  | initial size | initial value |
--------------------------------------------------------------------------------------
|GL_PIXEL_MAP_I_TO_I |	color index   |	 color index   |      1	      |	     0.0      |
|GL_PIXEL_MAP_S_TO_S | stencil index  |	stencil	index  |      1	      |	      0	      |
|GL_PIXEL_MAP_I_TO_R |	color index   |	      R	       |      1	      |	     0.0      |
|GL_PIXEL_MAP_I_TO_G |	color index   |	      G	       |      1	      |	     0.0      |
|GL_PIXEL_MAP_I_TO_B |	color index   |	      B	       |      1	      |	     0.0      |
|GL_PIXEL_MAP_I_TO_A |	color index   |	      A	       |      1	      |	     0.0      |
|GL_PIXEL_MAP_R_TO_R |	     R	      |	      R	       |      1	      |	     0.0      |
|GL_PIXEL_MAP_G_TO_G |	     G	      |	      G	       |      1	      |	     0.0      |
|GL_PIXEL_MAP_B_TO_B |	     B	      |	      B	       |      1	      |	     0.0      |
|GL_PIXEL_MAP_A_TO_A |	     A	      |	      A	       |      1	      |	     0.0      |
--------------------------------------------------------------------------------------

Errors

  GL_INVALID_ENUM is generated if map is not an	accepted value.

  GL_INVALID_VALUE is generated	if mapsize is negative or larger than
  GL_MAX_PIXEL_MAP_TABLE.

  GL_INVALID_VALUE is generated	if map is GL_PIXEL_MAP_I_TO_I,
  GL_PIXEL_MAP_S_TO_S, GL_PIXEL_MAP_I_TO_R, GL_PIXEL_MAP_I_TO_G,
  GL_PIXEL_MAP_I_TO_B, or GL_PIXEL_MAP_I_TO_A, and mapsize is not a power of
  two.

  GL_INVALID_OPERATION is generated if glPixelMap is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGetPixelMap
  glGet	with argument GL_PIXEL_MAP_I_TO_I_SIZE
  glGet	with argument GL_PIXEL_MAP_S_TO_S_SIZE
  glGet	with argument GL_PIXEL_MAP_I_TO_R_SIZE
  glGet	with argument GL_PIXEL_MAP_I_TO_G_SIZE
  glGet	with argument GL_PIXEL_MAP_I_TO_B_SIZE
  glGet	with argument GL_PIXEL_MAP_I_TO_A_SIZE
  glGet	with argument GL_PIXEL_MAP_R_TO_R_SIZE
  glGet	with argument GL_PIXEL_MAP_G_TO_G_SIZE
  glGet	with argument GL_PIXEL_MAP_B_TO_B_SIZE
  glGet	with argument GL_PIXEL_MAP_A_TO_A_SIZE
  glGet	with argument GL_MAX_PIXEL_MAP_TABLE

See Also

  glCopyPixels,	glDrawPixels, glPixelStore, glPixelTransfer, glReadPixels,
  glTexImage1D,	glTexImage2D

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glPixelStore.3gl

Name

  glPixelStoref, glPixelStorei - set pixel storage modes

C Specification

  void glPixelStoref( GLenum pname,
              GLfloat param )
  void glPixelStorei( GLenum pname,
              GLint param )

Parameters

  pname	 Specifies the symbolic	name of	the parameter to be set.  Six values
     affect	the packing of pixel data into memory: GL_PACK_SWAP_BYTES,
     GL_PACK_LSB_FIRST, GL_PACK_ROW_LENGTH,	GL_PACK_SKIP_PIXELS,
     GL_PACK_SKIP_ROWS, and	GL_PACK_ALIGNMENT.  Six	more affect the
     unpacking of pixel data from memory: GL_UNPACK_SWAP_BYTES,
     GL_UNPACK_LSB_FIRST, GL_UNPACK_ROW_LENGTH, GL_UNPACK_SKIP_PIXELS,
     GL_UNPACK_SKIP_ROWS, and GL_UNPACK_ALIGNMENT.

  param	 Specifies the value that pname	is set to.

Description

  glPixelStore sets pixel storage modes	that affect the	operation of
  subsequent glDrawPixels and glReadPixels as well as the unpacking of
  polygon stipple patterns (see	glPolygonStipple), bitmaps (see	glBitmap),
  and texture patterns (see glTexImage1D and glTexImage2D).

  pname	is a symbolic constant indicating the parameter	to be set, and param
  is the new value.  Six of the	twelve storage parameters affect how pixel
  data is returned to client memory, and are therefore significant only	for
  glReadPixels commands.  They are as follows:

  GL_PACK_SWAP_BYTES
        If true, byte ordering for multibyte color components, depth
        components,	color indices, or stencil indices is reversed.	That
        is,	if a four-byte component is made up of bytes b , b , b , b ,
                                                              0   1   2   3
        it is stored in memory as b	, b , b	, b  if	GL_PACK_SWAP_BYTES is
                                       3   2   1   0
        true.  GL_PACK_SWAP_BYTES has no effect on the memory order	of
        components within a	pixel, only on the order of bytes within
        components or indices.  For	example, the three components of a
        GL_RGB format pixel	are always stored with red first, green
        second, and	blue third, regardless of the value of
        GL_PACK_SWAP_BYTES.

  GL_PACK_LSB_FIRST
        If true, bits are ordered within a byte from least significant to
        most  significant; otherwise, the first bit	in each	byte is	the
        most significant one.  This	parameter is significant for bitmap
        data only.

  GL_PACK_ROW_LENGTH
        If greater than zero, GL_PACK_ROW_LENGTH defines the number	of
        pixels in a	row.  If the first pixel of a row is placed at
        location p in memory, then the location of the first pixel of the
        next row is	obtained by skipping

                 k = nl               if s >= a

                           snl
                 k = a/s·ceil( --- )  if s < a
                            a

        components or indices, where n is the number of components or
        indices in a pixel,	l is the number	of pixels in a row
        (GL_PACK_ROW_LENGTH	if it is greater than zero, the	width
        argument to	the pixel routine otherwise), a	is the value of
        GL_PACK_ALIGNMENT, and s is	the size, in bytes, of a single
        component (if a<s, then it is as if a=s).  In the case of 1-bit
        values, the	location of the	next row is obtained by	skipping

                          nl
                 k = 8a·ceil( -- )
                          8a

        components or indices.

        The	word component in this description refers to the nonindex
        values red,	green, blue, alpha, and	depth.	Storage	format
        GL_RGB, for	example, has three components per pixel: first red,
        then green,	and finally blue.

  GL_PACK_SKIP_PIXELS and GL_PACK_SKIP_ROWS
        These values are provided as a convenience to the programmer;
        they provide no functionality that cannot be duplicated simply by
        incrementing the pointer passed to glReadPixels.  Setting
        GL_PACK_SKIP_PIXELS	to i is	equivalent to incrementing the
        pointer by in components or	indices, where n is the	number of
        components or indices in each pixel.  Setting GL_PACK_SKIP_ROWS
        to j is equivalent to incrementing the pointer by jk components
        or indices,	where k	is the number of components or indices per
        row, as computed above in the GL_PACK_ROW_LENGTH section.

  GL_PACK_ALIGNMENT
        Specifies the alignment requirements for the start of each pixel
        row	in memory.  The	allowable values are 1 (byte-alignment), 2
        (rows aligned to even-numbered bytes), 4 (word alignment), and 8
        (rows start	on double-word boundaries).

  The other six	of the twelve storage parameters affect	how pixel data is
  read from client memory.  These values are significant for glDrawPixels,
  glTexImage1D,	glTexImage2D, glBitmap,	and glPolygonStipple.  They are	as
  follows:

  GL_UNPACK_SWAP_BYTES
       If true,	byte ordering for multibyte color components, depth
       components, color indices, or stencil indices is	reversed.  That	is,
       if a four-byte component	is made	up of bytes b ,	b , b ,	b , it is
                                                     0   1   2   3
       taken from memory as b ,	b , b ,	b  if GL_UNPACK_SWAP_BYTES is true.
                             3   2   1   0
       GL_UNPACK_SWAP_BYTES has	no effect on the memory	order of components
       within a	pixel, only on the order of bytes within components or
       indices.	 For example, the three	components of a	GL_RGB format pixel
       are always stored with red first, green second, and blue	third,
       regardless of the value of GL_UNPACK_SWAP_BYTES.

  GL_UNPACK_LSB_FIRST
       If true,	bits are ordered within	a byte from least significant to most
       significant; otherwise, the first bit in	each byte is the most
       significant one.	 This is significant for bitmap	data only.

  GL_UNPACK_ROW_LENGTH
       If greater than zero, GL_UNPACK_ROW_LENGTH defines the number of
       pixels in a row.	 If the	first pixel of a row is	placed at location p
       in memory, then the location of the first pixel of the next row is
       obtained	by skipping

                 k = nl               if s >= a

                           snl
                 k = a/s·ceil( --- )  if s < a
                            a

       components or indices, where n is the number of components or indices
       in a pixel, l is	the number of pixels in	a row (GL_UNPACK_ROW_LENGTH
       if it is	greater	than zero, the width argument to the pixel routine
       otherwise), a is	the value of GL_UNPACK_ALIGNMENT, and s	is the size,
       in bytes, of a single component (if a<s,	then it	is as if a=s).	In
       the case	of 1-bit values, the location of the next row is obtained by
       skipping

                          nl
                 k = 8a·ceil( -- )
                          8a

       components or indices.

       The word	component in this description refers to	the nonindex values
       red, green, blue, alpha,	and depth.  Storage format GL_RGB, for
       example,	has three components per pixel:	first red, then	green, and
       finally blue.

  GL_UNPACK_SKIP_PIXELS	and GL_UNPACK_SKIP_ROWS
       These values are	provided as a convenience to the programmer; they
       provide no functionality	that cannot be duplicated simply by
       incrementing the	pointer	passed to glDrawPixels,	glTexImage1D,
       glTexImage2D, glBitmap, or glPolygonStipple.  Setting
       GL_UNPACK_SKIP_PIXELS to	i is equivalent	to incrementing	the pointer
       by in components	or indices, where n is the number of components	or
       indices in each pixel.  Setting GL_UNPACK_SKIP_ROWS to j	is equivalent
       to incrementing the pointer by jk components or indices,	where k	is
       the number of components	or indices per row, as computed	above in the
       GL_UNPACK_ROW_LENGTH section.

  GL_UNPACK_ALIGNMENT
       Specifies the alignment requirements for	the start of each pixel	row
       in memory.  The allowable values	are 1 (byte-alignment),	2 (rows
       aligned to even-numbered	bytes),	4 (word	alignment), and	8 (rows	start
       on double-word boundaries).

  The following	table gives the	type, initial value, and range of valid
  values for each of the storage parameters that can be	set with
  glPixelStore.

       -----------------------------------------------------------------
       |   parameter name     |  type   | initial value |  valid range  |
       -----------------------------------------------------------------
       | GL_PACK_SWAP_BYTES   |	Boolean	|     false	| true or false	|
       |  GL_PACK_LSB_FIRST   |	Boolean	|     false	| true or false	|
       | GL_PACK_ROW_LENGTH   |	integer	|	0	|    [0,oo)	|
       |  GL_PACK_SKIP_ROWS   |	integer	|	0	|    [0,oo)	|
       | GL_PACK_SKIP_PIXELS  |	integer	|	0	|    [0,oo)	|
       |  GL_PACK_ALIGNMENT   |	integer	|	4	| 1, 2,	4, or 8	|
       |----------------------------------------------------------------|
       |GL_UNPACK_SWAP_BYTES  |	Boolean	|     false	| true or false	|
       | GL_UNPACK_LSB_FIRST  |	Boolean	|     false	| true or false	|
       |GL_UNPACK_ROW_LENGTH  |	integer	|	0	|    [0,oo)	|
       | GL_UNPACK_SKIP_ROWS  |	integer	|	0	|    [0,oo)	|
       |GL_UNPACK_SKIP_PIXELS |	integer	|	0	|    [0,oo)	|
       | GL_UNPACK_ALIGNMENT  | integer |       4       | 1, 2, 4, or 8 |
       -----------------------------------------------------------------

  glPixelStoref	can be used to set any pixel store parameter.  If the
  parameter type is Boolean, then if param is 0.0, the parameter is false;
  otherwise it is set to true.	If pname is a integer type parameter, param
  is rounded to	the nearest integer.

  Likewise, glPixelStorei can also be used to set any of the pixel store
  parameters.  Boolean parameters are set to false if param is 0 and true
  otherwise.  param is converted to floating point before being	assigned to
  real-valued parameters.

Notes

  The pixel storage modes in effect when glDrawPixels, glReadPixels,
  glTexImage1D,	glTexImage2D, glBitmap,	or glPolygonStipple is placed in a
  display list control the interpretation of memory data.  The pixel storage
  modes	in effect when a display list is executed are not significant.

Errors

  GL_INVALID_ENUM is generated if pname	is not an accepted value.

  GL_INVALID_VALUE is generated	if a negative row length, pixel	skip, or row
  skip value is	specified, or if alignment is specified	as other than 1, 2,
  4, or	8.

  GL_INVALID_OPERATION is generated if glPixelStore is called between a	call
  to glBegin and the corresponding call	to glEnd.

Associated Gets

  glGet	with argument GL_PACK_SWAP_BYTES
  glGet	with argument GL_PACK_LSB_FIRST
  glGet	with argument GL_PACK_ROW_LENGTH
  glGet	with argument GL_PACK_SKIP_ROWS
  glGet	with argument GL_PACK_SKIP_PIXELS
  glGet	with argument GL_PACK_ALIGNMENT
  glGet	with argument GL_UNPACK_SWAP_BYTES
  glGet	with argument GL_UNPACK_LSB_FIRST
  glGet	with argument GL_UNPACK_ROW_LENGTH
  glGet	with argument GL_UNPACK_SKIP_ROWS
  glGet	with argument GL_UNPACK_SKIP_PIXELS
  glGet	with argument GL_UNPACK_ALIGNMENT

See Also

  glBitmap, glDrawPixels, glPixelMap, glPixelTransfer, glPixelZoom,
  glPolygonStipple, glReadPixels, glTexImage1D,	glTexImage2D

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glPixelTransfer.3gl

Name

  glPixelTransferf, glPixelTransferi - set pixel transfer modes

C Specification

  void glPixelTransferf( GLenum	pname,
             GLfloat param )
  void glPixelTransferi( GLenum	pname,
             GLint param )

Parameters

  pname	 Specifies the symbolic	name of	the pixel transfer parameter to	be
     set.  Must be one of the following: GL_MAP_COLOR, GL_MAP_STENCIL,
     GL_INDEX_SHIFT, GL_INDEX_OFFSET, GL_RED_SCALE,	GL_RED_BIAS,
     GL_GREEN_SCALE, GL_GREEN_BIAS,	GL_BLUE_SCALE, GL_BLUE_BIAS,
     GL_ALPHA_SCALE, GL_ALPHA_BIAS,	GL_DEPTH_SCALE,	or GL_DEPTH_BIAS.

  param	 Specifies the value that pname	is set to.

Description

  glPixelTransfer sets pixel transfer modes that affect	the operation of
  subsequent glDrawPixels, glReadPixels, glCopyPixels, glTexImage1D, and
  glTexImage2D commands.  The algorithms that are specified by pixel transfer
  modes	operate	on pixels after	they are read from the frame buffer
  (glReadPixels	and glCopyPixels) or unpacked from client memory
  (glDrawPixels, glTexImage1D, and glTexImage2D).  Pixel transfer operations
  happen in the	same order, and	in the same manner, regardless of the command
  that resulted	in the pixel operation.	 Pixel storage modes (see
  glPixelStore)	control	the unpacking of pixels	being read from	client
  memory, and the packing of pixels being written back into client memory.

  Pixel	transfer operations handle four	fundamental pixel types: color,	color
  index, depth,	and stencil.  Color pixels are made up of four floating-point
  values with unspecified mantissa and exponent	sizes, scaled such that	0.0
  represents zero intensity and	1.0 represents full intensity.	Color indices
  comprise a single fixed-point	value, with unspecified	precision to the
  right	of the binary point.  Depth pixels comprise a single floating-point
  value, with unspecified mantissa and exponent	sizes, scaled such that	0.0
  represents the minimum depth buffer value, and 1.0 represents	the maximum
  depth	buffer value.  Finally,	stencil	pixels comprise	a single fixed-point
  value, with unspecified precision to the right of the	binary point.

  The pixel transfer operations	performed on the four basic pixel types	are
  as follows:

  Color	      Each of the four color components	is multiplied by a scale
          factor, then added to a bias factor.  That is, the red
          component	is multiplied by GL_RED_SCALE, then added to
          GL_RED_BIAS; the green component is multiplied by
          GL_GREEN_SCALE, then added to GL_GREEN_BIAS; the blue component
          is multiplied by GL_BLUE_SCALE, then added to GL_BLUE_BIAS; and
          the alpha	component is multiplied	by GL_ALPHA_SCALE, then	added
          to GL_ALPHA_BIAS.	 After all four	color components are scaled
          and biased, each is clamped to the range [0,1].  All color
          scale and	bias values are	specified with glPixelTransfer.

          If GL_MAP_COLOR is true, each color component is scaled by the
          size of the corresponding	color-to-color map, then replaced by
          the contents of that map indexed by the scaled component.	 That
          is, the red component is scaled by GL_PIXEL_MAP_R_TO_R_SIZE,
          then replaced by the contents of GL_PIXEL_MAP_R_TO_R indexed by
          itself.  The green component is scaled by
          GL_PIXEL_MAP_G_TO_G_SIZE,	then replaced by the contents of
          GL_PIXEL_MAP_G_TO_G indexed by itself.  The blue component is
          scaled by	GL_PIXEL_MAP_B_TO_B_SIZE, then replaced	by the
          contents of GL_PIXEL_MAP_B_TO_B indexed by itself.  And the
          alpha component is scaled	by GL_PIXEL_MAP_A_TO_A_SIZE, then
          replaced by the contents of GL_PIXEL_MAP_A_TO_A indexed by
          itself.  All components taken from the maps are then clamped to
          the range	[0,1].	GL_MAP_COLOR is	specified with
          glPixelTransfer.	The contents of	the various maps are
          specified	with glPixelMap.

  Color	index Each color index is shifted left by GL_INDEX_SHIFT bits,
          filling with zeros any bits beyond the number of fraction	bits
          carried by the fixed-point index.	 If GL_INDEX_SHIFT is
          negative,	the shift is to	the right, again zero filled.  Then
          GL_INDEX_OFFSET is added to the index.  GL_INDEX_SHIFT and
          GL_INDEX_OFFSET are specified with glPixelTransfer.

          From this	point, operation diverges depending on the required
          format of	the resulting pixels.  If the resulting	pixels are to
          be written to a color index buffer, or if	they are being read
          back to client memory in GL_COLOR_INDEX format, the pixels
          continue to be treated as	indices.  If GL_MAP_COLOR is true,
          each index is masked by 2n - 1, where n is
          GL_PIXEL_MAP_I_TO_I_SIZE,	then replaced by the contents of
          GL_PIXEL_MAP_I_TO_I indexed by the masked	value.	GL_MAP_COLOR
          is specified with	glPixelTransfer.  The contents of the index
          map are specified	with glPixelMap.

          If the resulting pixels are to be	written	to an RGBA color
          buffer, or if they are being read	back to	client memory in a
          format other than	GL_COLOR_INDEX,	the pixels are converted from
          indices to colors	by referencing the four	maps
          GL_PIXEL_MAP_I_TO_R, GL_PIXEL_MAP_I_TO_G,	GL_PIXEL_MAP_I_TO_B,
          and GL_PIXEL_MAP_I_TO_A.	Before being dereferenced, the index
          is masked	by 2n -	1, where n is GL_PIXEL_MAP_I_TO_R_SIZE for
          the red map, GL_PIXEL_MAP_I_TO_G_SIZE for	the green map,
          GL_PIXEL_MAP_I_TO_B_SIZE for the blue map, and
          GL_PIXEL_MAP_I_TO_A_SIZE for the alpha map.  All components
          taken from the maps are then clamped to the range	[0,1].	The
          contents of the four maps	are specified with glPixelMap.

  Depth	      Each depth value is multiplied by	GL_DEPTH_SCALE,	added to
          GL_DEPTH_BIAS, then clamped to the range [0,1].

  Stencil     Each index is shifted GL_INDEX_SHIFT bits	just as	a color	index
          is, then added to	GL_INDEX_OFFSET.  If GL_MAP_STENCIL is true,
          each index is masked by 2n - 1, where n is
          GL_PIXEL_MAP_S_TO_S_SIZE,	then replaced by the contents of
          GL_PIXEL_MAP_S_TO_S indexed by the masked	value.

  The following	table gives the	type, initial value, and range of valid
  values for each of the pixel transfer	parameters that	are set	with
  glPixelTransfer.

       ---------------------------------------------------------
       |    pname       |  type   | initial value | valid range |
       ---------------------------------------------------------
       | GL_MAP_COLOR   | Boolean |	    false     |	true/false  |
       |GL_MAP_STENCIL  | Boolean |	    false     |	true/false  |
       |GL_INDEX_SHIFT  | integer |	      0	      |	 (-oo,oo)   |
       |GL_INDEX_OFFSET | integer |	      0	      |	 (-oo,oo)   |
       | GL_RED_SCALE   |  float  |	     1.0      |	 (-oo,oo)   |
       |GL_GREEN_SCALE  |  float  |	     1.0      |	 (-oo,oo)   |
       | GL_BLUE_SCALE  |  float  |	     1.0      |	 (-oo,oo)   |
       |GL_ALPHA_SCALE  |  float  |	     1.0      |	 (-oo,oo)   |
       |GL_DEPTH_SCALE  |  float  |	     1.0      |	 (-oo,oo)   |
       |  GL_RED_BIAS   |  float  |	     0.0      |	 (-oo,oo)   |
       | GL_GREEN_BIAS  |  float  |	     0.0      |	 (-oo,oo)   |
       | GL_BLUE_BIAS   |  float  |	     0.0      |	 (-oo,oo)   |
       | GL_ALPHA_BIAS  |  float  |	     0.0      |	 (-oo,oo)   |
       | GL_DEPTH_BIAS  |  float  |	     0.0      |	 (-oo,oo)   |
       ---------------------------------------------------------

  glPixelTransferf can be used to set any pixel	transfer parameter.  If	the
  parameter type is Boolean, 0.0 implies false and any other value implies
  true.	 If pname is an	integer	parameter, param is rounded to the nearest
  integer.

  Likewise, glPixelTransferi can also be used to set any of the	pixel
  transfer parameters.	Boolean	parameters are set to false if param is	0 and
  true otherwise.  param is converted to floating point	before being assigned
  to real-valued parameters.

Notes

  If a glDrawPixels, glReadPixels, glCopyPixels, glTexImage1D, or
  glTexImage2D command is placed in a display list (see	glNewList and
  glCallList), the pixel transfer mode settings	in effect when the display
  list is executed are the ones	that are used.	They may be different from
  the settings when the	command	was compiled into the display list.

Errors

  GL_INVALID_ENUM is generated if pname	is not an accepted value.

  GL_INVALID_OPERATION is generated if glPixelTransfer is called between a
  call to glBegin and the corresponding	call to	glEnd.

Associated Gets

  glGet	with argument GL_MAP_COLOR
  glGet	with argument GL_MAP_STENCIL
  glGet	with argument GL_INDEX_SHIFT
  glGet	with argument GL_INDEX_OFFSET
  glGet	with argument GL_RED_SCALE
  glGet	with argument GL_RED_BIAS
  glGet	with argument GL_GREEN_SCALE
  glGet	with argument GL_GREEN_BIAS
  glGet	with argument GL_BLUE_SCALE
  glGet	with argument GL_BLUE_BIAS
  glGet	with argument GL_ALPHA_SCALE
  glGet	with argument GL_ALPHA_BIAS
  glGet	with argument GL_DEPTH_SCALE
  glGet	with argument GL_DEPTH_BIAS

See Also

  glCallList, glCopyPixels, glDrawPixels, glNewList, glPixelMap,
  glPixelStore,	glPixelZoom, glReadPixels, glTexImage1D, glTexImage2D

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glPixelZoom.3gl

Name

  glPixelZoom -	specify	the pixel zoom factors

C Specification

  void glPixelZoom( GLfloat xfactor,
            GLfloat yfactor )

Parameters

  xfactor, yfactor Specify the x and y zoom factors for	pixel write
           operations.

Description

  glPixelZoom specifies	values for the x and y zoom factors.  During the
  execution of glDrawPixels or glCopyPixels, if	(xr, yr) is the	current
  raster position, and a given element is in the mth row and nth column	of
  the pixel rectangle, then pixels whose centers are in	the rectangle with
  corners at

               (xr + n·xfactor,	yr + m·yfactor)

           (xr + (n+1)·xfactor,	yr + (m+1)·yfactor)

  are candidates for replacement.  Any pixel whose center lies on the bottom
  or left edge of this rectangular region is also modified.

  Pixel	zoom factors are not limited to	positive values.  Negative zoom
  factors reflect the resulting	image about the	current	raster position.

Errors

  GL_INVALID_OPERATION is generated if glPixelZoom is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_ZOOM_X
  glGet	with argument GL_ZOOM_Y

See Also

  glCopyPixels,	glDrawPixels

Introduction | Alphabetic | Specification

Last Edited: Tue May 23, 1995

AFV


glPointSize.3gl

Name

  glPointSize -	specify	the diameter of	rasterized points

C Specification

  void glPointSize( GLfloat size )

Parameters

  size	Specifies the diameter of rasterized points.  The default is 1.0.

Description

  glPointSize specifies	the rasterized diameter	of both	aliased	and
  antialiased points.  Using a point size other	than 1.0 has different
  effects, depending on	whether	point antialiasing is enabled.	Point
  antialiasing is controlled by	calling	glEnable and glDisable with argument
  GL_POINT_SMOOTH.

  If point antialiasing	is disabled, the actual	size is	determined by
  rounding the supplied	size to	the nearest integer.  (If the rounding
  results in the value 0, it is	as if the point	size were 1.) If the rounded
  size is odd, then the	center point (x, y) of the pixel fragment that
  represents the point is computed as

            ( floor(x )+.5, floor(y )+.5 )
                 w	           w

  where	w subscripts indicate window coordinates.  All pixels that lie within
  the square grid of the rounded size centered at (x, y) make up the
  fragment.  If	the size is even, the center point is

            ( floor(x +.5), floor(y +.5) )
                 w	           w

  and the rasterized fragment's	centers	are the	half-integer window
  coordinates within the square	of the rounded size centered at	(x, y).	 All
  pixel	fragments produced in rasterizing a nonantialiased point are assigned
  the same associated data, that of the	vertex corresponding to	the point.

  If antialiasing is enabled, then point rasterization produces	a fragment
  for each pixel square	that intersects	the region lying within	the circle
  having diameter equal	to the current point size and centered at the point's
  (x , y ).  The coverage value	for each fragment is the window	coordinate
    w   w
  area of the intersection of the circular region with the corresponding
  pixel	square.	 This value is saved and used in the final rasterization
  step.	The data associated with each fragment is the data associated with
  the point being rasterized.

  Not all sizes	are supported when point antialiasing is enabled. If an
  unsupported size is requested, the nearest supported size is used.  Only
  size 1.0 is guaranteed to be supported; others depend	on the
  implementation.  The range of	supported sizes	and the	size difference
  between supported sizes within the range can be queried by  calling glGet
  with arguments GL_POINT_SIZE_RANGE and GL_POINT_SIZE_GRANULARITY.

Notes

  The point size specified by glPointSize is always returned when
  GL_POINT_SIZE	is queried.  Clamping and rounding for aliased and
  antialiased points have no effect on the specified value.

  Non-antialiased point	size may be clamped to an implementation-dependent
  maximum.  Although this maximum cannot be queried, it	must be	no less	than
  the maximum value for	antialiased points, rounded to the nearest integer
  value.

Errors

  GL_INVALID_VALUE is generated	if size	is less	than or	equal to zero.

  GL_INVALID_OPERATION is generated if glPointSize is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_POINT_SIZE
  glGet	with argument GL_POINT_SIZE_RANGE
  glGet	with argument GL_POINT_SIZE_GRANULARITY
  glIsEnabled with argument GL_POINT_SMOOTH

See Also

  glEnable, glPointSmooth

Introduction | Alphabetic | Specification

Last Edited: Tue, May 23, 1995

AFV


glPolygonMode.3gl

Name

  glPolygonMode	- select a polygon rasterization mode

C Specification

  void glPolygonMode( GLenum face,
              GLenum mode )

Parameters

  face	Specifies the polygons that mode applies to.  Must be GL_FRONT for
    front-facing polygons, GL_BACK for back-facing polygons, or
    GL_FRONT_AND_BACK for front- and back-facing polygons.

  mode	Specifies the way polygons will	be rasterized.	Accepted values	are
    GL_POINT, GL_LINE, and GL_FILL.	 The default is	GL_FILL	for both
    front- and back-facing polygons.

Description

  glPolygonMode	controls the interpretation of polygons	for rasterization.
  face describes which polygons	mode applies to: front-facing polygons
  (GL_FRONT), back-facing polygons (GL_BACK), or both (GL_FRONT_AND_BACK).
  The polygon mode affects only	the final rasterization	of polygons.  In
  particular, a	polygon's vertices are lit and the polygon is clipped and
  possibly culled before these modes are applied.

  Three	modes are defined and can be specified in mode:

  GL_POINT	Polygon	vertices that are marked as the	start of a boundary
        edge are drawn as points.  Point attributes such as
        GL_POINT_SIZE and GL_POINT_SMOOTH control the rasterization
        of the points.	Polygon	rasterization attributes other than
        GL_POLYGON_MODE	have no	effect.

  GL_LINE	Boundary edges of the polygon are drawn	as line	segments.
        They are treated as connected line segments for	line
        stippling; the line stipple counter and	pattern	are not	reset
        between	segments (see glLineStipple).  Line attributes such
        as GL_LINE_WIDTH and GL_LINE_SMOOTH control the	rasterization
        of the lines.  Polygon rasterization attributes	other than
        GL_POLYGON_MODE	have no	effect.

  GL_FILL	The interior of	the polygon is filled.	Polygon	attributes
        such as	GL_POLYGON_STIPPLE and GL_POLYGON_SMOOTH control the
        rasterization of the polygon.

Examples

  To draw a surface with filled	back-facing polygons and outlined front-
  facing polygons, call	glPolygonMode(GL_FRONT,	GL_LINE);

Notes

  Vertices are marked as boundary or nonboundary with an edge flag.  Edge
  flags	are generated internally by the	GL when	it decomposes polygons,	and
  they can be set explicitly using glEdgeFlag.

Errors

  GL_INVALID_ENUM is generated if either face or mode is not an	accepted
  value.

  GL_INVALID_OPERATION is generated if glPolygonMode is	executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_POLYGON_MODE

See Also

  glBegin, glEdgeFlag, glLineStipple, glLineWidth, glPointSize,
  glPolygonStipple

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glPolygonStipple.3gl

Name

  glPolygonStipple - set the polygon stippling pattern

C Specification

  void glPolygonStipple( const GLubyte *mask )

Parameters

  mask	Specifies a pointer to a 32x32 stipple pattern that will be unpacked
    from memory in the same	way that glDrawPixels unpacks pixels.

Description

  Polygon stippling, like line stippling (see glLineStipple), masks out
  certain fragments produced by	rasterization, creating	a pattern.  Stippling
  is independent of polygon antialiasing.

  mask is a pointer to a 32x32 stipple pattern that is stored in memory	just
  like the pixel data supplied to a glDrawPixels with height and width both
  equal	to 32, a pixel format of GL_COLOR_INDEX, and data type of GL_BITMAP.
  That is, the stipple pattern is represented as a 32x32 array of 1-bit	color
  indices packed in unsigned bytes.  glPixelStore parameters like
  GL_UNPACK_SWAP_BYTES and GL_UNPACK_LSB_FIRST affect the assembling of	the
  bits into a stipple pattern.	Pixel transfer operations (shift, offset,
  pixel	map) are not applied to	the stipple image, however.

  Polygon stippling is enabled and disabled with glEnable and glDisable,
  using	argument GL_POLYGON_STIPPLE.  If enabled, a rasterized polygon
  fragment with	window coordinates x  and y  is	sent to	the next stage of the
                                    w      w
  GL if	and only if the	(x  mod 32)th bit is the (y  mod 32)th row of the
                          w                        w
  stipple pattern is one.  When	polygon	stippling is disabled, it is as	if
  the stipple pattern were all ones.

Errors

  GL_INVALID_OPERATION is generated if glPolygonStipple	is executed between
  the execution	of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGetPolygonStipple
  glIsEnabled with argument GL_POLYGON_STIPPLE

See Also

  glDrawPixels,	glLineStipple, glPixelStore, glPixelTransfer

Introduction | Alphabetic | Specification

Last Edited: Tue, May 23, 1995

AFV


glPushAttrib.3gl

Name

  glPushAttrib,	glPopAttrib - push and pop the attribute stack

C Specification

  void glPushAttrib( GLbitfield	mask )

Parameters

  mask	Specifies a mask that indicates	which attributes to save.  Values for
    mask are listed	in the table below.

C Specification

  void glPopAttrib( void )

Description

  glPushAttrib takes one argument, a mask that indicates which groups of
  state	variables to save on the attribute stack.  Symbolic constants are
  used to set bits in the mask.	 mask is typically constructed by ORing
  several of these constants together.	The special mask GL_ALL_ATTRIB_BITS
  can be used to save all stackable states.

  The symbolic mask constants and their	associated GL state are	as follows
  (the second column lists which attributes are	saved):

  GL_ACCUM_BUFFER_BIT	   Accumulation	buffer clear value

  GL_COLOR_BUFFER_BIT	   GL_ALPHA_TEST enable	bit
               Alpha test function and reference value
               GL_BLEND enable bit
               Blending source and destination functions
               GL_DITHER enable bit
               GL_DRAW_BUFFER setting
               GL_LOGIC_OP enable bit
               Logic op function
               Color mode and index	mode clear values
               Color mode and index	mode writemasks

  GL_CURRENT_BIT	   Current RGBA	color
               Current color index
               Current normal vector
               Current texture coordinates
               Current raster position
               GL_CURRENT_RASTER_POSITION_VALID flag
               RGBA	color associated with current raster position
               Color index associated with current raster position
               Texture coordinates associated with current raster position
               GL_EDGE_FLAG	flag

  GL_DEPTH_BUFFER_BIT	   GL_DEPTH_TEST enable	bit
               Depth buffer	test function
               Depth buffer	clear value
               GL_DEPTH_WRITEMASK enable bit

  GL_ENABLE_BIT		   GL_ALPHA_TEST flag
               GL_AUTO_NORMAL flag
               GL_BLEND flag
               Enable bits for the user-definable clipping planes
               GL_COLOR_MATERIAL
               GL_CULL_FACE	flag
               GL_DEPTH_TEST flag
               GL_DITHER flag
               GL_FOG flag
               GL_LIGHTi where 0 <=	i<GL_MAX_LIGHTS
               GL_LIGHTING flag
               GL_LINE_SMOOTH flag
               GL_LINE_STIPPLE flag
               GL_LOGIC_OP flag
               GL_MAP1_x where x is	a map type
               GL_MAP2_x where x is	a map type
               GL_NORMALIZE	flag
               GL_POINT_SMOOTH flag
               GL_POLYGON_SMOOTH flag
               GL_POLYGON_STIPPLE flag
               GL_SCISSOR_TEST flag
               GL_STENCIL_TEST flag
               GL_TEXTURE_1D flag
               GL_TEXTURE_2D flag
               Flags GL_TEXTURE_GEN_x where	x is S,	T, R, or Q

  GL_EVAL_BIT		   GL_MAP1_x enable bits, where	x is a map type
               GL_MAP2_x enable bits, where	x is a map type
               1-D grid endpoints and divisions
               2-D grid endpoints and divisions
               GL_AUTO_NORMAL enable bit

  GL_FOG_BIT		   GL_FOG enable flag
               Fog color
               Fog density
               Linear fog start
               Linear fog end
               Fog index
               GL_FOG_MODE value

  GL_HINT_BIT		   GL_PERSPECTIVE_CORRECTION_HINT setting
               GL_POINT_SMOOTH_HINT	setting
               GL_LINE_SMOOTH_HINT setting
               GL_POLYGON_SMOOTH_HINT setting
               GL_FOG_HINT setting

  GL_LIGHTING_BIT	   GL_COLOR_MATERIAL enable bit
               GL_COLOR_MATERIAL_FACE value
               Color material parameters that are tracking the current color
               Ambient scene color
               GL_LIGHT_MODEL_LOCAL_VIEWER value
               GL_LIGHT_MODEL_TWO_SIDE setting
               GL_LIGHTING enable bit
               Enable bit for each light
               Ambient, diffuse, and specular intensity for	each light
               Direction, position,	exponent, and cutoff angle for each light
               Constant, linear, and quadratic attenuation factors for each	light
               Ambient, diffuse, specular, and emissive color for each material
               Ambient, diffuse, and specular color	indices	for each material
               Specular exponent for each material
               GL_SHADE_MODEL setting

  GL_LINE_BIT		   GL_LINE_SMOOTH flag
               GL_LINE_STIPPLE enable bit
               Line	stipple	pattern	and repeat counter
               Line	width

  GL_LIST_BIT		   GL_LIST_BASE	setting

  GL_PIXEL_MODE_BIT	   GL_RED_BIAS and GL_RED_SCALE	settings
               GL_GREEN_BIAS and GL_GREEN_SCALE values
               GL_BLUE_BIAS	and GL_BLUE_SCALE
               GL_ALPHA_BIAS and GL_ALPHA_SCALE
               GL_DEPTH_BIAS and GL_DEPTH_SCALE
               GL_INDEX_OFFSET and GL_INDEX_SHIFT values
               GL_MAP_COLOR	and GL_MAP_STENCIL flags
               GL_ZOOM_X and GL_ZOOM_Y factors
               GL_READ_BUFFER setting

  GL_POINT_BIT		   GL_POINT_SMOOTH flag
               Point size

  GL_POLYGON_BIT	   GL_CULL_FACE	enable bit
               GL_CULL_FACE_MODE value
               GL_FRONT_FACE indicator
               GL_POLYGON_MODE setting
               GL_POLYGON_SMOOTH flag
               GL_POLYGON_STIPPLE enable bit

  GL_POLYGON_STIPPLE_BIT   Polygon stipple image

  GL_SCISSOR_BIT	   GL_SCISSOR_TEST flag
               Scissor box

  GL_STENCIL_BUFFER_BIT	   GL_STENCIL_TEST enable bit
               Stencil function and	reference value
               Stencil value mask
               Stencil fail, pass, and depth buffer	pass actions
               Stencil buffer clear	value
               Stencil buffer writemask

  GL_TEXTURE_BIT	   Enable bits for the four texture coordinates
               Border color	for each texture image
               Minification	function for each texture image
               Magnification function for each texture image
               Texture coordinates and wrap	mode for each texture image
               Color and mode for each texture environment
               Enable bits GL_TEXTURE_GEN_x, x is S, T, R, and Q
               GL_TEXTURE_GEN_MODE setting for S, T, R, and	Q
               glTexGen plane equations for	S, T, R, and Q

  GL_TRANSFORM_BIT	   Coefficients	of the six clipping planes
               Enable bits for the user-definable clipping planes
               GL_MATRIX_MODE value
               GL_NORMALIZE	flag

  GL_VIEWPORT_BIT	   Depth range (near and far)
               Viewport origin and extent

  glPopAttrib restores the values of the state variables saved with the	last
  glPushAttrib command.	 Those not saved are left unchanged.

  It is	an error to push attributes onto a full	stack, or to pop attributes
  off an empty stack.  In either case, the error flag is set and no other
  change is made to GL state.

  Initially, the attribute stack is empty.

Notes

  Not all values for GL	state can be saved on the attribute stack.  For
  example, pixel pack and unpack state,	render mode state, and select and
  feedback state cannot	be saved.

  The depth of the attribute stack depends on the implementation, but it must
  be at	least 16.

Errors

  GL_STACK_OVERFLOW is generated if glPushAttrib is called while the
  attribute stack is full.

  GL_STACK_UNDERFLOW is	generated if glPopAttrib is called while the
  attribute stack is empty.

  GL_INVALID_OPERATION is generated if glPushAttrib or glPopAttrib is
  executed between the execution of glBegin and	the corresponding execution
  of glEnd.

Associated Gets

  glGet	with argument GL_ATTRIB_STACK_DEPTH.
  glGet	with argument GL_MAX_ATTRIB_STACK_DEPTH.

See Also

  glGet, glGetClipPlane, glGetError, glGetLight, glGetMap, glGetMaterial,
  glGetPixelMap, glGetPolygonStipple, glGetString, glGetTexEnv,	glGetTexGen,
  glGetTexImage, glGetTexLevelParameter, glGetTexParameter, glIsEnabled

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glPushMatrix.3gl

Name

  glPushMatrix,	glPopMatrix - push and pop the current matrix stack

C Specification

  void glPushMatrix( void )

C Specification

  void glPopMatrix( void )

Description

  There	is a stack of matrices for each	of the matrix modes.  In GL_MODELVIEW
  mode,	the stack depth	is at least 32.	 In the	other two modes,
  GL_PROJECTION	and GL_TEXTURE,	the depth is at	least 2.  The current matrix
  in any mode is the matrix on the top of the stack for	that mode.

  glPushMatrix pushes the current matrix stack down by one, duplicating	the
  current matrix.  That	is, after a glPushMatrix call, the matrix on the top
  of the stack is identical to the one below it.

  glPopMatrix pops the current matrix stack, replacing the current matrix
  with the one below it	on the stack.

  Initially, each of the stacks	contains one matrix, an	identity matrix.

  It is	an error to push a full	matrix stack, or to pop	a matrix stack that
  contains only	a single matrix.  In either case, the error flag is set	and
  no other change is made to GL	state.

Errors

  GL_STACK_OVERFLOW is generated if glPushMatrix is called while the current
  matrix stack is full.

  GL_STACK_UNDERFLOW is	generated if glPopMatrix is called while the current
  matrix stack contains	only a single matrix.

  GL_INVALID_OPERATION is generated if glPushMatrix or glPopMatrix is
  executed between the execution of glBegin and	the corresponding execution
  of glEnd.

Associated Gets

  glGet	with argument GL_MATRIX_MODE
  glGet	with argument GL_MODELVIEW_MATRIX
  glGet	with argument GL_PROJECTION_MATRIX
  glGet	with argument GL_TEXTURE_MATRIX
  glGet	with argument GL_MODELVIEW_STACK_DEPTH
  glGet	with argument GL_PROJECTION_STACK_DEPTH
  glGet	with argument GL_TEXTURE_STACK_DEPTH
  glGet	with argument GL_MAX_MODELVIEW_STACK_DEPTH
  glGet	with argument GL_MAX_PROJECTION_STACK_DEPTH
  glGet	with argument GL_MAX_TEXTURE_STACK_DEPTH

See Also

  glFrustum, glLoadIdentity, glLoadMatrix, glMatrixMode, glMultMatrix,
  glOrtho, glRotate, glScale, glTranslate, glViewport

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glPushName.3gl

Name

  glPushName, glPopName	- push and pop the name	stack

C Specification

  void glPushName( GLuint name )

Parameters

  name	Specifies a name that will be pushed onto the name stack.

C Specification

  void glPopName( void )

Description

  The name stack is used during	selection mode to allow	sets of	rendering
  commands to be uniquely identified.  It consists of an ordered set of
  unsigned integers.  glPushName causes	name to	be pushed onto the name
  stack, which is initially empty.  glPopName pops one name off	the top	of
  the stack.

  It is	an error to push a name	onto a full stack, or to pop a name off	an
  empty	stack.	It is also an error to manipulate the name stack between the
  execution of glBegin and the corresponding execution of glEnd.  In any of
  these	cases, the error flag is set and no other change is made to GL state.

  The name stack is always empty while the render mode is not GL_SELECT.
  Calls	to glPushName or glPopName while the render mode is not	GL_SELECT are
  ignored.

Errors

  GL_STACK_OVERFLOW is generated if glPushName is called while the name	stack
  is full.

  GL_STACK_UNDERFLOW is	generated if glPopName is called while the name	stack
  is empty.

  GL_INVALID_OPERATION is generated if glPushName or glPopName is executed
  between a call to glBegin and	the corresponding call to glEnd.

Associated Gets

  glGet	with argument GL_NAME_STACK_DEPTH
  glGet	with argument GL_MAX_NAME_STACK_DEPTH

See Also

  glInitNames, glLoadName, glRenderMode, glSelectBuffer

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glRasterPos.3gl

Name

  glRasterPos2d, glRasterPos2f,	glRasterPos2i, glRasterPos2s, glRasterPos3d,
  glRasterPos3f, glRasterPos3i,	glRasterPos3s, glRasterPos4d, glRasterPos4f,
  glRasterPos4i, glRasterPos4s,	glRasterPos2dv,	glRasterPos2fv,
  glRasterPos2iv, glRasterPos2sv, glRasterPos3dv, glRasterPos3fv,
  glRasterPos3iv, glRasterPos3sv, glRasterPos4dv, glRasterPos4fv,
  glRasterPos4iv, glRasterPos4sv - specify the raster position for pixel
  operations

C Specification

  void glRasterPos2d( GLdouble x,
              GLdouble y )
  void glRasterPos2f( GLfloat x,
              GLfloat y	)
  void glRasterPos2i( GLint x,
              GLint y )
  void glRasterPos2s( GLshort x,
              GLshort y	)
  void glRasterPos3d( GLdouble x,
              GLdouble y,
              GLdouble z )
  void glRasterPos3f( GLfloat x,
              GLfloat y,
              GLfloat z	)
  void glRasterPos3i( GLint x,
              GLint y,
              GLint z )
  void glRasterPos3s( GLshort x,
              GLshort y,
              GLshort z	)
  void glRasterPos4d( GLdouble x,
              GLdouble y,
              GLdouble z,
              GLdouble w )
  void glRasterPos4f( GLfloat x,
              GLfloat y,
              GLfloat z,
              GLfloat w	)
  void glRasterPos4i( GLint x,
              GLint y,
              GLint z,
              GLint w )
  void glRasterPos4s( GLshort x,
              GLshort y,
              GLshort z,
              GLshort w	)

Parameters

  x, y,	z, w
       Specify the x, y, z,	and w object coordinates (if present) for the
       raster position.

C Specification

  void glRasterPos2dv( const GLdouble *v )
  void glRasterPos2fv( const GLfloat *v	)
  void glRasterPos2iv( const GLint *v )
  void glRasterPos2sv( const GLshort *v	)
  void glRasterPos3dv( const GLdouble *v )
  void glRasterPos3fv( const GLfloat *v	)
  void glRasterPos3iv( const GLint *v )
  void glRasterPos3sv( const GLshort *v	)
  void glRasterPos4dv( const GLdouble *v )
  void glRasterPos4fv( const GLfloat *v	)
  void glRasterPos4iv( const GLint *v )
  void glRasterPos4sv( const GLshort *v	)

  v    Specifies a pointer to an array of two, three, or four elements,
       specifying x, y,	z, and w coordinates, respectively.

Description

  The GL maintains a 3-D position in window coordinates.  This position,
  called the raster position, is maintained with subpixel accuracy.  It	is
  used to position pixel and bitmap write operations.  See glBitmap,
  glDrawPixels,	and glCopyPixels.

  The current raster position consists of three	window coordinates (x, y, z),
  a clip coordinate w value, an	eye coordinate distance, a valid bit, and
  associated color data	and texture coordinates.  The w	coordinate is a	clip
  coordinate, because w	is not projected to window coordinates.	 glRasterPos4
  specifies object coordinates x, y, z,	and w explicitly.  glRasterPos3
  specifies object coordinate x, y, and	z explicitly, while w is implicitly
  set to one.  glRasterPos2 uses the argument values for x and y while
  implicitly setting z and w to	zero and one.

  The object coordinates presented by glRasterPos are treated just like	those
  of a glVertex	command: They are transformed by the current modelview and
  projection matrices and passed to the	clipping stage.	 If the	vertex is not
  culled, then it is projected and scaled to window coordinates, which become
  the new current raster position, and the GL_CURRENT_RASTER_POSITION_VALID
  flag is set.	If the vertex is culled, then the valid	bit is cleared and
  the current raster position and associated color and texture coordinates
  are undefined.

  The current raster position also includes some associated color data and
  texture coordinates.	If lighting is enabled,	then GL_CURRENT_RASTER_COLOR,
  in RGBA mode,	or the GL_CURRENT_RASTER_INDEX,	in color index mode, is	set
  to the color produced	by the lighting	calculation (see glLight,
  glLightModel,	and glShadeModel).  If lighting	is disabled, current color
  (in RGBA mode, state variable	GL_CURRENT_COLOR) or color index (in color
  index	mode, state variable GL_CURRENT_INDEX) is used to update the current
  raster color.

  Likewise, GL_CURRENT_RASTER_TEXTURE_COORDS is	updated	as a function of
  GL_CURRENT_TEXTURE_COORDS, based on the texture matrix and the texture
  generation functions (see glTexGen).	Finally, the distance from the origin
  of the eye coordinate	system to the vertex as	transformed by only the
  modelview matrix replaces GL_CURRENT_RASTER_DISTANCE.

  Initially, the current raster	position is (0,0,0,1), the current raster
  distance is 0, the valid bit is set, the associated RGBA color is
  (1,1,1,1), the associated color index	is 1, and the associated texture
  coordinates are (0, 0, 0, 1).	 In RGBA mode, GL_CURRENT_RASTER_INDEX is
  always 1; in color index mode, the current raster RGBA color always
  maintains its	initial	value.

Notes

  The raster position is modified both by glRasterPos and by glBitmap.

  When the raster position coordinates are invalid, drawing commands that are
  based	on the raster position are ignored (that is, they do not result	in
  changes to GL	state).

Errors

  GL_INVALID_OPERATION is generated if glRasterPos is called between a call
  to glBegin and the corresponding call	to glEnd.

Associated Gets

  glGet	with argument GL_CURRENT_RASTER_POSITION
  glGet	with argument GL_CURRENT_RASTER_POSITION_VALID
  glGet	with argument GL_CURRENT_RASTER_DISTANCE
  glGet	with argument GL_CURRENT_RASTER_COLOR
  glGet	with argument GL_CURRENT_RASTER_INDEX
  glGet	with argument GL_CURRENT_RASTER_TEXTURE_COORDS

See Also

  glBitmap, glCopyPixels, glDrawPixels,	glLight, glLightModel, glShadeModel,
  glTexCoord, glTexGen,	glVertex

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glReadBuffer.3gl

Name

  glReadBuffer - select	a color	buffer source for pixels

C Specification

  void glReadBuffer( GLenum mode )

Parameters

  mode	Specifies a color buffer.  Accepted values are GL_FRONT_LEFT,
    GL_FRONT_RIGHT,	GL_BACK_LEFT, GL_BACK_RIGHT, GL_FRONT, GL_BACK,
    GL_LEFT, GL_RIGHT, and GL_AUXi,	where i	is between 0 and
    GL_AUX_BUFFERS -1.

Description

  glReadBuffer specifies a color buffer	as the source for subsequent
  glReadPixels and glCopyPixels	commands.  mode	accepts	one of twelve or more
  predefined values.  (GL_AUX0 through GL_AUX3 are always defined.) In a
  fully	configured system, GL_FRONT, GL_LEFT, and GL_FRONT_LEFT	all name the
  front	left buffer, GL_FRONT_RIGHT and	GL_RIGHT name the front	right buffer,
  and GL_BACK_LEFT and GL_BACK name the	back left buffer.

  Nonstereo double-buffered configurations have	only a front left and a	back
  left buffer.	Single-buffered	configurations have a front left and a front
  right	buffer if stereo, and only a front left	buffer if nonstereo.  It is
  an error to specify a	nonexistent buffer to glReadBuffer.

  By default, mode is GL_FRONT in single-buffered configurations, and GL_BACK
  in double-buffered configurations.

Errors

  GL_INVALID_ENUM is generated if mode is not one of the twelve	(or more)
  accepted values.

  GL_INVALID_OPERATION is generated if mode specifies a	buffer that does not
  exist.

  GL_INVALID_OPERATION is generated if glReadBuffer is executed	between	the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_READ_BUFFER

See Also

  glCopyPixels,	glDrawBuffer, glReadPixels

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glReadPixels.3gl

Name

  glReadPixels - read a	block of pixels	from the frame buffer

C Specification

  void glReadPixels( GLint x,
             GLint y,
             GLsizei width,
             GLsizei height,
             GLenum format,
             GLenum type,
             GLvoid *pixels )

Parameters

  x, y Specify the window coordinates of the first pixel that is read from
       the frame buffer.  This location	is the lower left corner of a
       rectangular block of pixels.

  width, height
       Specify the dimensions of the pixel rectangle.  width and height	of
       one correspond to a single pixel.

  format
       Specifies the format of the pixel data.	The following symbolic values
       are accepted: GL_COLOR_INDEX, GL_STENCIL_INDEX, GL_DEPTH_COMPONENT,
       GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_RGB, GL_RGBA, GL_LUMINANCE,
       and GL_LUMINANCE_ALPHA.

  type Specifies the data type of the pixel data.  Must	be one of
       GL_UNSIGNED_BYTE, GL_BYTE, GL_BITMAP, GL_UNSIGNED_SHORT,	GL_SHORT,
       GL_UNSIGNED_INT,	GL_INT,	or GL_FLOAT.

  pixels
       Returns the pixel data.

Description

  glReadPixels returns pixel data from the frame buffer, starting with the
  pixel	whose lower left corner	is at location (x, y), into client memory
  starting at location pixels.	Several	parameters control the processing of
  the pixel data before	it is placed into client memory.  These	parameters
  are set with three commands: glPixelStore, glPixelTransfer, and glPixelMap.
  This reference page describes	the effects on glReadPixels of most, but not
  all of the parameters	specified by these three commands.

  glReadPixels returns values from each	pixel with lower left-hand corner at
  (x + i, y + j) for 0<=i<width and 0<=j<height. This pixel is said to be the
  ith pixel in the jth row. Pixels are returned	in row order from the lowest
  to the highest row, left to right in each row.

  format specifies the format for the returned pixel values.  Accepted values
  for format are as follows:

  GL_COLOR_INDEX
        Color indices are read from	the color buffer selected by
        glReadBuffer.  Each	index is converted to fixed point, shifted
        left or right depending on the value and sign of GL_INDEX_SHIFT,
        and	added to GL_INDEX_OFFSET. If GL_MAP_COLOR is GL_TRUE, indices
        are	replaced by their mappings in the table	GL_PIXEL_MAP_I_TO_I.

  GL_STENCIL_INDEX
        Stencil values are read from the stencil buffer.  Each index is
        converted to fixed point, shifted left or right depending on the
        value and sign of GL_INDEX_SHIFT, and added	to GL_INDEX_OFFSET.
        If GL_MAP_STENCIL is GL_TRUE, indices are replaced by their
        mappings in	the table GL_PIXEL_MAP_S_TO_S.

  GL_DEPTH_COMPONENT
        Depth values are read from the depth buffer.  Each component is
        converted to floating point	such that the minimum depth value
        maps to 0.0	and the	maximum	value maps to 1.0.  Each component is
        then multiplied by GL_DEPTH_SCALE, added to	GL_DEPTH_BIAS, and
        finally clamped to the range [0,1].

  GL_RED

  GL_GREEN

  GL_BLUE

  GL_ALPHA

  GL_RGB

  GL_RGBA

  GL_LUMINANCE

  GL_LUMINANCE_ALPHA
        Processing differs depending on whether color buffers store	color
        indices or RGBA color components.  If color	indices	are stored,
        they are read from the color buffer	selected by glReadBuffer.
        Each index is converted to fixed point, shifted left or right
        depending on the value and sign of GL_INDEX_SHIFT, and added to
        GL_INDEX_OFFSET. Indices are then replaced by the red, green,
        blue, and alpha values obtained by indexing	the
        GL_PIXEL_MAP_I_TO_R, GL_PIXEL_MAP_I_TO_G, GL_PIXEL_MAP_I_TO_B,
        and	GL_PIXEL_MAP_I_TO_A tables.  Each of these tables must be of
                  n
        size 2 , but n may be different for different tables. Before an
                                                                 n
        index is used to look up a value in a table of size 2 it must
                               n
        be masked against 2 -1.

        If RGBA color components are stored	in the color buffers, they
        are	read from the color buffer selected by glReadBuffer.  Each
        color component is converted to floating point such	that zero
        intensity maps to 0.0 and full intensity maps to 1.0.  Each
        component is then multiplied by GL_c_SCALE and added to
        GL_c_BIAS, where c is RED, GREEN, BLUE, or ALPHA.  Finally,	if
        GL_MAP_COLOR is GL_TRUE, Each component is clamped to the range
        [0,1], scaled to the size of its corresponding table, and is then
        replaced by	its mapping in the table GL_PIXEL_MAP_c_TO_c, where c
        is R, G, B,	or A.

        Unneeded data is then discarded.  For example, GL_RED discards
        the	green, blue, and alpha components, while GL_RGB	discards only
        the	alpha component.  GL_LUMINANCE computes	a single component
        value as the sum of	the red, green,	and blue components, and
        GL_LUMINANCE_ALPHA does the	same, while keeping alpha as a second
        value.  The	final values are clamped to the	range [0,1].

  The shift, scale, bias, and lookup factors described above are all
  specified by glPixelTransfer.	 The lookup table contents themselves are
  specified by glPixelMap.

  The final step involves converting the indices or components to the proper
  format, as specified by type.	 If format is GL_COLOR_INDEX or
  GL_STENCIL_INDEX and type is not GL_FLOAT, each index	is masked with the
  mask value given in the following table.  If type is GL_FLOAT, then each
  integer index	is converted to	single-precision floating-point	format.

  If format is GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_RGB, GL_RGBA,
  GL_LUMINANCE,	or GL_LUMINANCE_ALPHA and type is not GL_FLOAT,	each
  component is multiplied by the multiplier shown in the following table.  If
  type is GL_FLOAT, then each component	is passed as is	(or converted to the
  client's single-precision floating-point format if it	is different from the
  one used by the GL).

        -------------------------------------------------------
        |      type        | index mask | component conversion |
        -------------------------------------------------------
        |GL_UNSIGNED_BYTE  |   2^8-1    |	    (2^8-1)c	   |
        |	  GL_BYTE      |   2^7-1    |	 [(2^8-1)c-1]/2	   |
        |	 GL_BITMAP     |     1	    |	       1	   |
        |GL_UNSIGNED_SHORT |   2^16-1   |	   (2^16-1)c	   |
        |	 GL_SHORT      |   2^15-1   |	[(2^16-1)c-1]/2	   |
        | GL_UNSIGNED_INT  |   2^32-1   |	   (2^32-1)c	   |
        |	  GL_INT       |   2^31-1   |	[(2^32-1)c-1]/2	   |
        |    GL_FLOAT      |    none    |          c           |
        -------------------------------------------------------

  Return values	are placed in memory as	follows.  If format is
  GL_COLOR_INDEX, GL_STENCIL_INDEX, GL_DEPTH_COMPONENT,	GL_RED,	GL_GREEN,
  GL_BLUE, GL_ALPHA, or	GL_LUMINANCE, a	single value is	returned and the data
  for the ith pixel in the jth row is placed in	location (j) width + i.
  GL_RGB returns three values, GL_RGBA returns four values, and
  GL_LUMINANCE_ALPHA returns two values	for each pixel,	with all values
  corresponding	to a single pixel occupying contiguous space in	pixels.
  Storage parameters set by glPixelStore, such as GL_PACK_SWAP_BYTES and
  GL_PACK_LSB_FIRST, affect the	way that data is written into memory. See
  glPixelStore for a description.

Notes

  Values for pixels that lie outside the window	connected to the current GL
  context are undefined.

  If an	error is generated, no change is made to the contents of pixels.

Errors

  GL_INVALID_ENUM is generated if format or type is not an accepted value.

  GL_INVALID_VALUE is generated	if either width	or height is negative.

  GL_INVALID_OPERATION is generated if format is GL_COLOR_INDEX and the	color
  buffers store	RGBA color components.

  GL_INVALID_OPERATION is generated if format is GL_STENCIL_INDEX and there
  is no	stencil	buffer.

  GL_INVALID_OPERATION is generated if format is GL_DEPTH_COMPONENT and	there
  is no	depth buffer.

  GL_INVALID_OPERATION is generated if glReadPixels is executed	between	the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_INDEX_MODE

See Also

  glCopyPixels,	glDrawPixels, glPixelMap, glPixelStore,	glPixelTransfer,
  glReadBuffer

Introduction | Alphabetic | Specification

Last Edited: Tue, May 23, 1995

AFV


glRect.3gl

Name

  glRectd, glRectf, glRecti, glRects, glRectdv,	glRectfv, glRectiv, glRectsv
  - draw a rectangle

C Specification

  void glRectd(	GLdouble x1,
        GLdouble y1,
        GLdouble x2,
        GLdouble y2 )
  void glRectf(	GLfloat	x1,
        GLfloat	y1,
        GLfloat	x2,
        GLfloat	y2 )
  void glRecti(	GLint x1,
        GLint y1,
        GLint x2,
        GLint y2 )
  void glRects(	GLshort	x1,
        GLshort	y1,
        GLshort	x2,
        GLshort	y2 )

Parameters

  x1, y1 Specify one vertex of a rectangle.

  x2, y2 Specify the opposite vertex of	the rectangle.

C Specification

  void glRectdv( const GLdouble	*v1,
         const GLdouble	*v2 )
  void glRectfv( const GLfloat *v1,
         const GLfloat *v2 )
  void glRectiv( const GLint *v1,
         const GLint *v2 )
  void glRectsv( const GLshort *v1,
         const GLshort *v2 )

Parameters

  v1   Specifies a pointer to one vertex of a rectangle.

  v2   Specifies a pointer to the opposite vertex of the rectangle.

Description

  glRect supports efficient specification of rectangles	as two corner points.
  Each	rectangle command takes	four arguments,	organized either as two
  consecutive pairs of (x,y) coordinates, or as	two pointers to	arrays,	each
  containing an	(x,y) pair.  The resulting rectangle is	defined	in the z=0
  plane.

  glRect(x1, y1, x2, y2) is exactly equivalent to the following	sequence:
  glBegin(GL_POLYGON); glVertex2(x1, y1); glVertex2(x2,	y1); glVertex2(x2,
  y2); glVertex2(x1, y2); glEnd(); Note	that if	the second vertex is above
  and to the right of the first	vertex,	the  rectangle is constructed with a
  counterclockwise winding.

Errors

  GL_INVALID_OPERATION is generated if glRect is called	between	a call to
  glBegin and the corresponding	call to	glEnd.

See Also

  glBegin, glVertex

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glRenderMode.3gl

Name

  glRenderMode - set rasterization mode

C Specification

  GLint	glRenderMode( GLenum mode )

Parameters

  mode	Specifies the rasterization mode.  Three values	are accepted:
    GL_RENDER, GL_SELECT, and GL_FEEDBACK.	The default value is
    GL_RENDER.

Description

  glRenderMode sets the	rasterization mode.  It	takes one argument, mode,
  which	can assume one of three	predefined values:

  GL_RENDER	  Render mode. Primitives are rasterized, producing pixel
          fragments, which are written into the	frame buffer.  This
          is the normal	mode and also the default mode.

  GL_SELECT	  Selection mode.  No pixel fragments are produced, and	no
          change to the	frame buffer contents is made.	Instead, a
          record of the	names of primitives that would have been
          drawn	if the render mode was GL_RENDER is returned in	a
          select buffer, which must be created (see glSelectBuffer)
          before selection mode	is entered.

  GL_FEEDBACK	  Feedback mode.  No pixel fragments are produced, and no
          change to the	frame buffer contents is made.	Instead, the
          coordinates and attributes of	vertices that would have been
          drawn	had the	render mode been GL_RENDER is returned in a
          feedback buffer, which must be created (see
          glFeedbackBuffer) before feedback mode is entered.

  The return value of glRenderMode is determined by the	render mode at the
  time glRenderMode is called, rather than by mode.  The values	returned for
  the three render modes are as	follows:

  GL_RENDER	  Zero.

  GL_SELECT	  The number of	hit records transferred	to the select buffer.

  GL_FEEDBACK	  The number of	values (not vertices) transferred to the
          feedback buffer.

  Refer	to the glSelectBuffer and glFeedbackBuffer reference pages for more
  details concerning selection and feedback operation.

Notes

  If an	error is generated, glRenderMode returns zero regardless of the
  current render mode.

Errors

  GL_INVALID_ENUM is generated if mode is not one of the three accepted
  values.

  GL_INVALID_OPERATION is generated if glSelectBuffer is called	while the
  render mode is GL_SELECT, or if glRenderMode is called with argument
  GL_SELECT before glSelectBuffer is called at least once.

  GL_INVALID_OPERATION is generated if glFeedbackBuffer	is called while	the
  render mode is GL_FEEDBACK, or if glRenderMode is called with	argument
  GL_FEEDBACK before glFeedbackBuffer is called	at least once.

  GL_INVALID_OPERATION is generated if glRenderMode is executed	between	the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_RENDER_MODE

See Also

  glFeedbackBuffer, glInitNames, glLoadName, glPassThrough, glPushName,
  glSelectBuffer

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glRotate.3gl

Name

  glRotated, glRotatef - multiply the current matrix by	a rotation matrix

C Specification

  void glRotated( GLdouble angle,
          GLdouble x,
          GLdouble y,
          GLdouble z )
  void glRotatef( GLfloat angle,
          GLfloat x,
          GLfloat y,
          GLfloat z )

Parameters

  angle	 Specifies the angle of	rotation, in degrees.

  x, y,	z
     Specify the x,	y, and z coordinates of	a vector, respectively.

Description

  glRotate computes a matrix that performs a counterclockwise rotation of
  angle	degrees	about the vector from the origin through the point (x, y, z).

  The current matrix (see glMatrixMode)	is multiplied by this rotation
  matrix, with the product replacing the current matrix.  That is, if M	is
  the current matrix and R is the translation matrix, then M is	replaced with
  M · R.

  If the matrix	mode is	either GL_MODELVIEW or GL_PROJECTION, all objects
  drawn	after glRotate is called are rotated.  Use glPushMatrix	and
  glPopMatrix to save and restore the unrotated	coordinate system.

Errors

  GL_INVALID_OPERATION is generated if glRotate is executed between
  the execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_MATRIX_MODE
  glGet	with argument GL_MODELVIEW_MATRIX
  glGet	with argument GL_PROJECTION_MATRIX
  glGet	with argument GL_TEXTURE_MATRIX

See Also

  glMatrixMode,	glMultMatrix, glPushMatrix, glScale, glTranslate

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glScale.3gl

Name

  glScaled, glScalef - multiply	the current matrix by a	general	scaling
  matrix

C Specification

  void glScaled( GLdouble x,
         GLdouble y,
         GLdouble z )
  void glScalef( GLfloat x,
         GLfloat y,
         GLfloat z )

Parameters

  x, y,	z
     Specify scale factors along the x, y, and z axes, respectively.

Description

  glScale produces a general scaling along the x, y, and z axes.  The three
  arguments indicate the desired scale factors along each of the three axes.
  The resulting	matrix is

                  |	 x   0	 0   0	|
                  |			|
                  |	 0   y	 0   0	|
                  |			|
                  |	 0   0	 z   0	|
                  |			|
                  |	 0   0	 0   1	|

  The current matrix (see glMatrixMode)	is multiplied by this scale matrix,
  with the product replacing the current matrix.  That is, if M	is the
  current matrix and S is the scale matrix, then M is replaced with M ·	S.

  If the matrix	mode is	either GL_MODELVIEW or GL_PROJECTION, all objects
  drawn	after glScale is called	are scaled.  Use glPushMatrix and glPopMatrix
  to save and restore the unscaled coordinate system.

Notes

  If scale factors other than 1.0 are applied to the modelview matrix and
  lighting is enabled, automatic normalization of normals should probably
  also be enabled (glEnable and	glDisable with argument	GL_NORMALIZE).

Errors

  GL_INVALID_OPERATION is generated if glScale is executed between
  the execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_MATRIX_MODE
  glGet	with argument GL_MODELVIEW_MATRIX
  glGet	with argument GL_PROJECTION_MATRIX
  glGet	with argument GL_TEXTURE_MATRIX

See Also

  glMatrixMode,	glMultMatrix, glPushMatrix, glRotate, glTranslate

Introduction | Alphabetic | Specification

Last Edited: Tue, May 23, 1995

AFV


glScissor.3gl

Name

  glScissor - define the scissor box

C Specification

  void glScissor( GLint	x,
          GLint	y,
          GLsizei width,
          GLsizei height )

Parameters

  x, y Specify the lower left corner of	the scissor box.  Initially (0,0).

  width, height
       Specify the width and height of the scissor box.	 When a	GL context is
       first attached to a window, width and height are	set to the dimensions
       of that window.

Description

  The glScissor	routine	defines	a rectangle, called the	scissor	box, in
  window coordinates.  The first two arguments,	x and y, specify the lower
  left corner of the box.  width and height specify the	width and height of
  the box.

  The scissor test is enabled and disabled using glEnable and glDisable	with
  argument GL_SCISSOR_TEST.  While the scissor test is enabled,	only pixels
  that lie within the scissor box can be modified by drawing commands.
  Window coordinates have integer values at the	shared corners of frame
  buffer pixels, so  glScissor(0,0,1,1)	allows only the	lower left pixel in
  the window to	be modified,  and glScissor(0,0,0,0) disallows modification
  to all pixels	in the window.

  When the scissor test	is disabled, it	is as though the scissor box includes
  the entire window.

Errors

  GL_INVALID_VALUE is generated	if either width	or height is negative.

  GL_INVALID_OPERATION is generated if glScissor is executed between
  execution of glBegin and the corresponding execution ofglEnd.

Associated Gets

  glGet	with argument GL_SCISSOR_BOX
  glIsEnabled with argument GL_SCISSOR_TEST

See Also

  glEnable, glViewport

Introduction | Alphabetic | Specification

Last Edited: Tue, May 23, 1995

AFV


glSelectBuffer.3gl

Name

  glSelectBuffer - establish a buffer for selection mode values

C Specification

  void glSelectBuffer( GLsizei size,
               GLuint *buffer )

Parameters

  size	  Specifies the	size of	buffer.

  buffer  Returns the selection	data.

Description

  glSelectBuffer has two arguments: buffer is a	pointer	to an array of
  unsigned integers, and size indicates	the size of the	array.	buffer
  returns values from the name stack (see glInitNames, glLoadName,
  glPushName) when the rendering mode is GL_SELECT (see	glRenderMode).
  glSelectBuffer must be issued	before selection mode is enabled, and it must
  not be issued	while the rendering mode is GL_SELECT.

  Selection is used by a programmer to determine which primitives are drawn
  into some region of a	window.	 The region is defined by the current
  modelview and	perspective matrices.

  In selection mode, no	pixel fragments	are produced from rasterization.
  Instead, if a	primitive intersects the clipping volume defined by the
  viewing frustum and the user-defined clipping	planes,	this primitive causes
  a selection hit.  (With polygons, no hit occurs if the polygon is culled.)
  When a change	is made	to the name stack, or when glRenderMode	is called, a
  hit record is	copied to buffer if any	hits have occurred since the last
  such event (name stack change	or glRenderMode	call).	The hit	record
  consists of the number of names in the name stack at the time	of the event,
  followed by the minimum and maximum depth values of all vertices that	hit
  since	the previous event, followed by	the name stack contents, bottom	name
  first.

  Returned depth values	are mapped such	that the largest unsigned integer
  value	corresponds to window coordinate depth 1.0, and	zero corresponds to
  window coordinate depth 0.0.

  An internal index into buffer	is reset to zero whenever selection mode is
  entered.  Each time a	hit record is copied into buffer, the index is
  incremented to point to the cell just	past the end of	the block of names -
  that is, to the next available cell.	If the hit record is larger than the
  number of remaining locations	in buffer, as much data	as can fit is copied,
  and the overflow flag	is set.	 If the	name stack is empty when a hit record
  is copied, that record consists of zero followed by the minimum and maximum
  depth	values.

  Selection mode is exited by calling glRenderMode with	an argument other
  than GL_SELECT.  Whenever glRenderMode is called while the render mode is
  GL_SELECT, it	returns	the number of hit records copied to buffer, resets
  the overflow flag and	the selection buffer pointer, and initializes the
  name stack to	be empty.  If the overflow bit was set when glRenderMode was
  called, a negative hit record	count is returned.

Notes

  The contents of buffer are undefined until glRenderMode is called with an
  argument other than GL_SELECT.

  glBegin/glEnd	primitives and calls to	glRasterPos can	result in hits.

Errors

  GL_INVALID_VALUE is generated	if size	is negative.

  GL_INVALID_OPERATION is generated if glSelectBuffer is called	while the
  render mode is GL_SELECT, or if glRenderMode is called with argument
  GL_SELECT before glSelectBuffer is called at least once.

  GL_INVALID_OPERATION is generated if glSelectBuffer is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_NAME_STACK_DEPTH

See Also

  glFeedbackBuffer, glInitNames, glLoadName, glPushName, glRenderMode

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glShadeModel.3gl

Name

  glShadeModel - select	flat or	smooth shading

C Specification

  void glShadeModel( GLenum mode )

Parameters

  mode	Specifies a symbolic value representing	a shading technique.
    Accepted values	are GL_FLAT and	GL_SMOOTH.  The	default	is GL_SMOOTH.

Description

  GL primitives	can have either	flat or	smooth shading.	 Smooth	shading, the
  default, causes the computed colors of vertices to be	interpolated as	the
  primitive is rasterized, typically assigning different colors	to each
  resulting pixel fragment.  Flat shading selects the computed color of	just
  one vertex and assigns it to all the pixel fragments generated by
  rasterizing a	single primitive.  In either case, the computed	color of a
  vertex is the	result of lighting, if lighting	is enabled, or it is the
  current color	at the time the	vertex was specified, if lighting is
  disabled.

  Flat and smooth shading are indistinguishable	for points.  Counting
  vertices and primitives from one starting when glBegin is issued, each
  flat-shaded line segment i is	given the computed color of vertex i+1,	its
  second vertex.  Counting similarly from one, each flat-shaded	polygon	is
  given	the computed color of the vertex listed	in the following table.	 This
  is the last vertex to	specify	the polygon in all cases except	single
  polygons, where the first vertex specifies the flat-shaded color.

            ---------------------------------------
            |primitive type of polygon   |   i    |
            ---------------------------------------
            |Single polygon (i=1)	 |   1	  |
            |Triangle strip		 |  i+2	  |
            |Triangle fan		 |  i+2	  |
            |Independent triangle	 |   3i	  |
            |Quad strip			 |  2i+2  |
            |Independent quad		 |   4i	  |
            ---------------------------------------

  Flat and smooth shading are specified	by glShadeModel	with mode set to
  GL_FLAT and GL_SMOOTH, respectively.

Errors

  GL_INVALID_ENUM is generated if mode is any value other than GL_FLAT or
  GL_SMOOTH.

  GL_INVALID_OPERATION is generated if glShadeModel is executed	between	the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet with argument GL_SHADE_MODEL

See Also

  glBegin, glColor, glLight, glLightModel

Introduction | Alphabetic | Specification

Last Edited: Tue, May 23, 1995

AFV


glStencilFunc.3gl

Name

  glStencilFunc	- set function and reference value for stencil testing

C Specification

  void glStencilFunc( GLenum func,
              GLint ref,
              GLuint mask )

Parameters

  func	Specifies the test function.  Eight tokens are valid: GL_NEVER,
    GL_LESS, GL_LEQUAL, GL_GREATER,	GL_GEQUAL, GL_EQUAL, GL_NOTEQUAL, and
    GL_ALWAYS.

  ref	Specifies the reference	value for the stencil test.  ref is clamped
                     n
    to the range [0,2 -1], where n is the number of	bitplanes in the
    stencil	buffer.

  mask	Specifies a mask that is ANDed with both the reference value and the
    stored stencil value when the test is done.

Description

  Stenciling, like z-buffering,	enables	and disables drawing on	a per-pixel
  basis.  You draw into	the stencil planes using GL drawing primitives,	then
  render geometry and images, using the	stencil	planes to mask out portions
  of the screen.  Stenciling is	typically used in multipass rendering
  algorithms to	achieve	special	effects, such as decals, outlining, and
  constructive solid geometry rendering.

  The stencil test conditionally eliminates a pixel based on the outcome of a
  comparison between the reference value and the value in the stencil buffer.
  The test is enabled by glEnable and glDisable	with argument
  GL_STENCIL_TEST.  Actions taken based	on the outcome of the stencil test
  are specified	with glStencilOp.

  func is a symbolic constant that determines the stencil comparison
  function.  It	accepts	one of eight values, shown below.  ref is an integer
  reference value that is used in the stencil comparison.  It is clamped to
  the range [0,2n-1], where n is the number of bitplanes in the	stencil
  buffer.  mask	is bitwise ANDed with both the reference value and the stored
  stencil value, with the ANDed	values participating in	the comparison.

  If stencil represents	the value stored in the	corresponding stencil buffer
  location, the	following list shows the effect	of each	comparison function
  that can be specified	by func.  Only if the comparison succeeds is the
  pixel	passed through to the next stage in the	rasterization process (see
  glStencilOp).	 All tests treat stencil values	as unsigned integers in	the
  range	[0,2n-1], where	n is the number	of bitplanes in the stencil buffer.

  Here are the values accepted by func:

  GL_NEVER	    Always fails.

  GL_LESS	    Passes if ( ref & mask ) < ( stencil & mask ).

  GL_LEQUAL	    Passes if ( ref & mask ) <= ( stencil & mask ).

  GL_GREATER	    Passes if ( ref & mask ) > ( stencil & mask ).

  GL_GEQUAL	    Passes if ( ref & mask ) >= ( stencil & mask ).

  GL_EQUAL	    Passes if ( ref & mask ) = ( stencil & mask ).

  GL_NOTEQUAL	    Passes if ( ref & mask ) != ( stencil & mask ).

  GL_ALWAYS	    Always passes.

Notes

  Initially, the stencil test is disabled.  If there is	no stencil buffer, no
  stencil modification can occur and it	is as if the stencil test always
  passes.

Errors

  GL_INVALID_ENUM is generated if func is not one of the eight accepted
  values.

  GL_INVALID_OPERATION is generated if glStencilFunc is	executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_STENCIL_FUNC
  glGet	with argument GL_STENCIL_VALUE_MASK
  glGet	with argument GL_STENCIL_REF
  glGet	with argument GL_STENCIL_BITS
  glIsEnabled with argument GL_STENCIL_TEST

See Also

  glAlphaFunc, glBlendFunc, glDepthFunc, glEnable, glIsEnabled,	glLogicOp,
  glStencilOp

Introduction | Alphabetic | Specification

Last Edited: Tue, May 23, 1995

AFV


glStencilMask.3gl

Name

  glStencilMask	- control the writing of individual bits in the	stencil
  planes

C Specification

  void glStencilMask( GLuint mask )

Parameters

  mask	Specifies a bit	mask to	enable and disable writing of individual bits
    in the stencil planes.	Initially, the mask is all ones.

Description

  glStencilMask	controls the writing of	individual bits	in the stencil
  planes.  The least significant n bits	of mask, where n is the	number of
  bits in the stencil buffer, specify a	mask.  Wherever	a one appears in the
  mask,	the corresponding bit in the stencil buffer is made writable.  Where
  a zero appears, the bit is write-protected.  Initially, all bits are
  enabled for writing.

Errors

  GL_INVALID_OPERATION is generated if glStencilMask is	executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_STENCIL_WRITEMASK
  glGet	with argument GL_STENCIL_BITS

See Also

  glColorMask, glDepthMask, glIndexMask, glStencilFunc,	glStencilOp

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glStencilOp.3gl

Name

  glStencilOp -	set stencil test actions

C Specification

  void glStencilOp( GLenum fail,
            GLenum zfail,
            GLenum zpass )

Parameters

  fail	 Specifies the action to take when the stencil test fails.  Six
     symbolic constants are	accepted: GL_KEEP, GL_ZERO, GL_REPLACE,
     GL_INCR, GL_DECR, and GL_INVERT.

  zfail	 Specifies stencil action when the stencil test	passes,	but the	depth
     test fails.  zfail accepts the	same symbolic constants	as fail.

  zpass	 Specifies stencil action when both the	stencil	test and the depth
     test pass, or when the	stencil	test passes and	either there is	no
     depth buffer or depth testing is not enabled.	zpass accepts the
     same symbolic constants as fail.

Description

  Stenciling, like z-buffering,	enables	and disables drawing on	a per-pixel
  basis.  You draw into	the stencil planes using GL drawing primitives,	then
  render geometry and images, using the	stencil	planes to mask out portions
  of the screen.  Stenciling is	typically used in multipass rendering
  algorithms to	achieve	special	effects, such as decals, outlining, and
  constructive solid geometry rendering.

  The stencil test conditionally eliminates a pixel based on the outcome of a
  comparison between the value in the stencil buffer and a reference value.
  The test is enabled with glEnable and	glDisable calls	with argument
  GL_STENCIL_TEST, and controlled with glStencilFunc.

  glStencilOp takes three arguments that indicate what happens to the stored
  stencil value	while stenciling is enabled.  If the stencil test fails, no
  change is made to the	pixel's	color or depth buffers,	and fail specifies
  what happens to the stencil buffer contents.	The six	possible actions are
  as follows:

  GL_KEEP	  Keeps	the current value.

  GL_ZERO	  Sets the stencil buffer value	to zero.

  GL_REPLACE	  Sets the stencil buffer value	to ref,	as specified by
          glStencilFunc.

  GL_INCR	  Increments the current stencil buffer	value.	Clamps to the
          maximum representable	unsigned value.

  GL_DECR	  Decrements the current stencil buffer	value.	Clamps to
          zero.

  GL_INVERT	  Bitwise inverts the current stencil buffer value.

  Stencil buffer values	are treated as unsigned	integers.  When	incremented
  and decremented, values are clamped to 0 and 2n-1, where n is	the value
  returned by querying GL_STENCIL_BITS.

  The other two	arguments to glStencilOp specify stencil buffer	actions
  should subsequent depth buffer tests succeed (zpass) or fail (zfail).	 (See
  glDepthFunc.)	They are specified using the same six symbolic constants as
  fail.	 Note that zfail is ignored when there is no depth buffer, or when
  the depth buffer is not enabled.  In these cases, fail and zpass specify
  stencil action when the stencil test fails and passes, respectively.

Notes

  Initially the	stencil	test is	disabled.  If there is no stencil buffer, no
  stencil modification can occur and it	is as if the stencil tests always
  pass,	regardless of any call to glStencilOp.

Errors

  GL_INVALID_ENUM is generated if fail,	zfail, or zpass	is any value other
  than the six defined constant	values.

  GL_INVALID_OPERATION is generated if glStencilOp is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_STENCIL_FAIL
  glGet	with argument GL_STENCIL_PASS_DEPTH_PASS
  glGet	with argument GL_STENCIL_PASS_DEPTH_FAIL
  glGet	with argument GL_STENCIL_BITS
  glIsEnabled with argument GL_STENCIL_TEST

See Also

  glAlphaFunc, glBlendFunc, glDepthFunc, glEnable, glLogicOp, glStencilFunc

Introduction | Alphabetic | Specification

Last Edited: Tue, May 23, 1995

AFV


glTexCoord.3gl

Name

  glTexCoord1d,	glTexCoord1f, glTexCoord1i, glTexCoord1s, glTexCoord2d,
  glTexCoord2f,	glTexCoord2i, glTexCoord2s, glTexCoord3d, glTexCoord3f,
  glTexCoord3i,	glTexCoord3s, glTexCoord4d, glTexCoord4f, glTexCoord4i,
  glTexCoord4s,	glTexCoord1dv, glTexCoord1fv, glTexCoord1iv, glTexCoord1sv,
  glTexCoord2dv, glTexCoord2fv,	glTexCoord2iv, glTexCoord2sv, glTexCoord3dv,
  glTexCoord3fv, glTexCoord3iv,	glTexCoord3sv, glTexCoord4dv, glTexCoord4fv,
  glTexCoord4iv, glTexCoord4sv - set the current texture coordinates

C Specification

  void glTexCoord1d( GLdouble s	)
  void glTexCoord1f( GLfloat s )
  void glTexCoord1i( GLint s )
  void glTexCoord1s( GLshort s )
  void glTexCoord2d( GLdouble s,
             GLdouble t	)
  void glTexCoord2f( GLfloat s,
             GLfloat t )
  void glTexCoord2i( GLint s,
             GLint t )
  void glTexCoord2s( GLshort s,
             GLshort t )
  void glTexCoord3d( GLdouble s,
             GLdouble t,
             GLdouble r	)
  void glTexCoord3f( GLfloat s,
             GLfloat t,
             GLfloat r )
  void glTexCoord3i( GLint s,
             GLint t,
             GLint r )
  void glTexCoord3s( GLshort s,
             GLshort t,
             GLshort r )
  void glTexCoord4d( GLdouble s,
             GLdouble t,
             GLdouble r,
             GLdouble q	)
  void glTexCoord4f( GLfloat s,
             GLfloat t,
             GLfloat r,
             GLfloat q )
  void glTexCoord4i( GLint s,
             GLint t,
             GLint r,
             GLint q )
  void glTexCoord4s( GLshort s,
             GLshort t,
             GLshort r,
             GLshort q )

Parameters

  s, t,	r, q
       Specify s, t, r, and	q  texture coordinates.	 Not all parameters
       are present in all forms of the command.

C Specification

  void glTexCoord1dv( const GLdouble *v	)
  void glTexCoord1fv( const GLfloat *v )
  void glTexCoord1iv( const GLint *v )
  void glTexCoord1sv( const GLshort *v )
  void glTexCoord2dv( const GLdouble *v	)
  void glTexCoord2fv( const GLfloat *v )
  void glTexCoord2iv( const GLint *v )
  void glTexCoord2sv( const GLshort *v )
  void glTexCoord3dv( const GLdouble *v	)
  void glTexCoord3fv( const GLfloat *v )
  void glTexCoord3iv( const GLint *v )
  void glTexCoord3sv( const GLshort *v )
  void glTexCoord4dv( const GLdouble *v	)
  void glTexCoord4fv( const GLfloat *v )
  void glTexCoord4iv( const GLint *v )
  void glTexCoord4sv( const GLshort *v )

Parameters

  v    Specifies a pointer to an array of one, two, three, or four elements,
       which in	turn specify the s, t, r, and q	texture	coordinates.

Description

  The current texture coordinates are part of the data that is associated
  with each vertex and with the	current	raster position.  They are set with
  glTexCoord.

  glTexCoord specifies texture coordinates in one, two,	three, or four
  dimensions. glTexCoord1 sets the current texture coordinates to (s, 0, 0,
  1); a	call to	glTexCoord2 sets them to (s, t,	0, 1).	Similarly,
  glTexCoord3 specifies	the texture coordinates	as (s, t, r, 1), and
  glTexCoord4 defines all four components explicitly as	(s, t, r, q).

Notes

  The current texture coordinates can be updated at any	time.  In particular,
  glTexCoord can be called between a call to glBegin and the corresponding
  call to glEnd.

Associated Gets

  glGet	with argument GL_CURRENT_TEXTURE_COORDS

See Also

  glVertex

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glTexCoordPointerEXT.3gl

Name

  glTexCoordPointerEXT - define	an array of texture coordinates

C Specification

  void glTexCoordPointerEXT( GLint size,
                 GLenum type,
                 GLsizei stride,
                 GLsizei count,
                 const GLvoid *pointer )

Parameters

  size	   Specifies the number	of coordinates per array element. It must be
       1, 2, 3 or 4.

  type	   Specifies the data type of each texture coordinate.	Symbolic
       constants GL_SHORT, GL_INT, GL_FLOAT, or GL_DOUBLE_EXT, are
       accepted.

  stride   Specifies the byte offset between consecutive array elements.  If
       stride is zero the array elements are understood to be tightly
       packed.

  count	   Specifies the number	of array elements, counting from the first,
       that	are static.

  pointer  Specifies a pointer to the first coordinate of the first element
       in the array.

Description

  glTexCoordPointerEXT specifies the location and data format of an array of
  texture coordinates to use when rendering using the vertex array extension.
  size specifies the number of coordinates per element, and must be 1, 2, 3,
  or 4.  type specifies the data type of each texture coordinate and stride
  gives the byte stride from one array element to the next allowing vertexes
  and attributes to be packed into a single array or stored in separate arrays.
  (Single-array storage may be more efficient on some implementations.) count
  indicates the number of array elements (counting from the first) that are
  static. Static elements may be modified by the application, but once they
  are modified, the application must explicitly respecify the array before
  using it for any rendering. When a texture coordinate array is specified,
  size, type, stride, count, and pointer are saved  as client-side state, and
  static array elements may be cached by the implementation.

  The texture coordinate array is enabled and disabled using glEnable and
  glDisable with the argument GL_TEXTURE_COORD_ARRAY_EXT. If enabled, the
  texture coordinate array is used when	glDrawArraysEXT	or glArrayElementEXT
  is called.

  Use glDrawArraysEXT to define	a sequence of primitives (all of the same
  type)	from pre-specified vertex and vertex attribute arrays.	Use
  glArrayElementEXT to specify primitives by indexing vertexes and vertex
  attributes.

Notes

  Non-static array elements are	not accessed until glArrayElementEXT or
  glDrawArraysEXT is executed.

  By default the texture coordinate array is disabled and it won't be
  accessed when	glArrayElementEXT or glDrawArraysEXT is	called.

  Although it is not an error to call glTexCoordPointerEXT between the
  execution of glBegin and the corresponding execution of glEnd, the results
  are undefined.

  glTexCoordPointerEXT will typically be implemented on	the client side	with
  no protocol.

  Since	the texture coordinate array parameters	are client side	state, they
  are not saved	or restored by glPushAttrib and	glPopAttrib.

  glTexCoordPointerEXT commands	are not	entered	into display lists.

  glTexCoordPointerEXT is part of the EXT_vertex_array extension,
  not part of the core GL command set. If "GL_EXT_vertex_array" is
  included in the string returned by glGetString, when called with argument
  GL_EXTENSIONS, extension EXT_vertex_array is supported.

Errors

  GL_INVALID_VALUE is generated	if size	is not 1, 2, 3,	or 4.

  GL_INVALID_ENUM is generated if type is not an accepted value.

  GL_INVALID_VALUE is generated	if stride or count  is negative

Associated Gets

  glIsEnabled with argument GL_TEXTURE_COORD_ARRAY_EXT
  glGet	with argument GL_TEXTURE_COORD_ARRAY_SIZE_EXT
  glGet	with argument GL_TEXTURE_COORD_ARRAY_TYPE_EXT
  glGet	with argument GL_TEXTURE_COORD_ARRAY_STRIDE_EXT
  glGet	with argument GL_TEXTURE_COORD_ARRAY_COUNT_EXT
  glGetPointervEXT with	argument GL_TEXTURE_COORD_ARRAY_POINTER_EXT

See Also

  glArrayElementEXT, glColorPointerEXT,	glDrawArraysEXT,
  glEdgeFlagPointerEXT,	glGetPointervEXT, glIndexPointerEXT,
  glNormalPointerEXT, glVertexPointerEXT, glEnable

Introduction | Alphabetic | Specification

Last Edited: Fri Feb 24, 1995

AFV


glTexEnv.3gl

Name

  glTexEnvf, glTexEnvi,	glTexEnvfv, glTexEnviv - set texture environment
  parameters

C Specification

  void glTexEnvf( GLenum target,
          GLenum pname,
          GLfloat param	)
  void glTexEnvi( GLenum target,
          GLenum pname,
          GLint	param )

Parameters

  target  Specifies a texture environment.  Must be GL_TEXTURE_ENV.

  pname	  Specifies the	symbolic name of a single-valued texture environment
      parameter.  Must be GL_TEXTURE_ENV_MODE.

  param	  Specifies a single symbolic constant,	one of GL_MODULATE, GL_DECAL,
      or GL_BLEND.

C Specification

  void glTexEnvfv( GLenum target,
           GLenum pname,
           const GLfloat *params )
  void glTexEnviv( GLenum target,
           GLenum pname,
           const GLint *params )

Parameters

  target
       Specifies a texture environment.	 Must be GL_TEXTURE_ENV.

  pname
       Specifies the symbolic name of a	texture	environment parameter.
       Accepted	values are GL_TEXTURE_ENV_MODE and GL_TEXTURE_ENV_COLOR.

  params
       Specifies a pointer to an array of parameters: either a single
       symbolic	constant or an RGBA color.

Description

  A texture environment	specifies how texture values are interpreted when a
  fragment is textured.	 target	must be	GL_TEXTURE_ENV.	 pname can be either
  GL_TEXTURE_ENV_MODE or GL_TEXTURE_ENV_COLOR.

  If pname is GL_TEXTURE_ENV_MODE, then	params is (or points to) the symbolic
  name of a texture function.  Three texture functions are defined:
  GL_MODULATE, GL_DECAL, and GL_BLEND

  A texture function acts on the fragment to be	textured using the texture
  image	value that applies to the fragment (see	glTexParameter)	and produces
  an RGBA color	for that fragment.  The	following table	shows how the RGBA
  color	is produced for	each of	the three texture functions that can be
  chosen.  C is	a triple of color values (RGB) and A is	the associated alpha
  value.  RGBA values extracted	from a texture image are in the	range [0,1].
  The subscript	f refers to the	incoming fragment, the subscript t to the
  texture image, the subscript c to the	texture	environment color, and
  subscript v indicates	a value	produced by the	texture	function.

  A texture image can have up to four components per texture element (see
  glTexImage1D and glTexImage2D).  In a	one-component image, L	indicates
                                                              t
  that single component.  A two-component image	uses L	and A .	 A three-
                                                      t      t
  component image has only a color value, C .  A four-component	image has
                                           t
  both a color value C	and an alpha value A .
              t			    t

       ----------------------------------------------------------------
       | number of ||		 texture functions		       |
       |components || GL_MODULATE      GL_DECAL           GL_BLEND     |
       ----------------------------------------------------------------
       |     	   ||  C =L C	 |		    | C	=(1-L )C +L C  |
       |    1	   ||	v  t f	 |    undefined	    |  v     t	f  t c |
       |           ||  A =A      |                  | A =A             |
       |           ||   v  f     |                  |  v  f            |
       |-----------||------------|------------------|------------------|
       |     	   ||  C =L C 	 |             	    | C =(1-L )C +L C  |
       |    2	   ||	v  t f	 |    undefined	    |  v     t	f  t c |
       |           ||  A =A A    |                  | A =A A           |
       |	   ||	v  t f	 |             	    |  v  t f          |
       |-----------||------------|------------------|------------------|
       |	   ||  C =C C	 |	C =C	    |		       |
       |    3	   ||	v  t f	 |	 v  t	    |	 undefined     |
       |           ||  A =A      |      A =A        |                  |
       |           ||   v  f     |       v  f       |                  |
       |-----------||------------|------------------|------------------|
       |           ||  C =C C    | C =(1-A )C +A C  |                  |
       |    4	   ||   v  t f	 |  v     t  f  t t |	 undefined     |
       |           ||  A =A A    |      A =A        |                  |
       |           ||   v  t f   |       v  f       |                  |
       ----------------------------------------------------------------

  If pname is GL_TEXTURE_ENV_COLOR, params is a pointer to an array that
  holds	an RGBA	color consisting of four values.  Integer color	components
  are interpreted linearly such	that the most positive integer maps to 1.0,
  and the most negative	integer	maps to	-1.0.  The values are clamped to the
  range	[0,1] when they	are specified.	C  takes these four values.
                     c
  GL_TEXTURE_ENV_MODE defaults to GL_MODULATE and GL_TEXTURE_ENV_COLOR
  defaults to (0,0,0,0).

Errors

  GL_INVALID_ENUM is generated when target or pname is not one of the
  accepted defined values, or when params should have a	defined	constant
  value	(based on the value of pname) and does not.

  GL_INVALID_OPERATION is generated if glTexEnv is executed between
  the execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGetTexEnv

See Also

  glTexImage1D,	glTexImage2D, glTexParameter

Introduction | Alphabetic | Specification

Last Edited: Tue, May 23, 1995

AFV


glTexGen.3gl

Name

  glTexGend, glTexGenf,	glTexGeni, glTexGendv, glTexGenfv, glTexGeniv -
  control the generation of texture coordinates

C Specification

  void glTexGend( GLenum coord,
          GLenum pname,
          GLdouble param )
  void glTexGenf( GLenum coord,
          GLenum pname,
          GLfloat param	)
  void glTexGeni( GLenum coord,
          GLenum pname,
          GLint	param )

Parameters

  coord	  Specifies a texture coordinate.  Must	be one of the following:
      GL_S,	GL_T, GL_R, or GL_Q.

  pname	  Specifies the	symbolic name of the texture-coordinate	generation
      function.  Must be GL_TEXTURE_GEN_MODE.

  param	  Specifies a single-valued texture generation parameter, one of
      GL_OBJECT_LINEAR, GL_EYE_LINEAR, or GL_SPHERE_MAP.

C Specification

  void glTexGendv( GLenum coord,
           GLenum pname,
           const GLdouble *params )
  void glTexGenfv( GLenum coord,
           GLenum pname,
           const GLfloat *params )
  void glTexGeniv( GLenum coord,
           GLenum pname,
           const GLint *params )

Parameters

  coord
       Specifies a texture coordinate.	Must be	one of the following: GL_S,
       GL_T, GL_R, or GL_Q.

  pname
       Specifies the symbolic name of the texture-coordinate generation
       function	or function parameters.	 Must be GL_TEXTURE_GEN_MODE,
       GL_OBJECT_PLANE,	or GL_EYE_PLANE.

  params
       Specifies a pointer to an array of texture generation parameters.  If
       pname is	GL_TEXTURE_GEN_MODE, then the array must contain a single
       symbolic	constant, one of GL_OBJECT_LINEAR, GL_EYE_LINEAR, or
       GL_SPHERE_MAP. Otherwise, params	holds the coefficients for the
       texture-coordinate generation function specified	by pname.

Description

  glTexGen selects a texture-coordinate	generation function or supplies
  coefficients for one of the functions.  coord	names one of the (s,t,r,q)
  texture coordinates, and it must be one of these symbols: GL_S, GL_T,	GL_R,
  or GL_Q.  pname must be one of three symbolic	constants:
  GL_TEXTURE_GEN_MODE, GL_OBJECT_PLANE,	or GL_EYE_PLANE. If pname is
  GL_TEXTURE_GEN_MODE, then params chooses a mode, one of GL_OBJECT_LINEAR,
  GL_EYE_LINEAR, or GL_SPHERE_MAP. If pname is either GL_OBJECT_PLANE or
  GL_EYE_PLANE,	params contains	coefficients for the corresponding texture
  generation function.

  If the texture generation function is	GL_OBJECT_LINEAR, the function

                 g=p x +p y	+p z +p	w
                1 o  2 o  3 o  4 o

  is used, where g is the value	computed for the coordinate named in coord,
  p , p	, p , and p  are the four values supplied in params, and x , y , z ,
   1   2   3       4                                              o   o   o
  and w  are the object	coordinates of the vertex.  This function can be used
       o
  to texture-map terrain using sea level as a reference	plane (defined by p ,
                                                                           1
  p , p	, and p	). The altitude	of a terrain vertex is computed	by the
   2   3       4
  GL_OBJECT_LINEAR coordinate generation function as its distance from sea
  level; that altitude is used to index	the texture image to map white snow
  onto peaks and green grass onto foothills, for example.

  If the texture generation function is	GL_EYE_LINEAR, the function

               g=p'x' + p'y	 + p'z  + p'w
                  1 e    2 e    3 e	   4 e

  is used, where
                                                        -1
           (p'	p'  p'  p') = (p   p   p   p )·M
             1	 2   3   4	1   2	3   4

  and x	, y , z	, and w	 are the eye coordinates of the	vertex,	p , p ,	p ,
       e   e   e       e                                         1   2   3
  and p  are the values supplied in params, and M is the modelview matrix
       4
  when glTexGen	is invoked.  If	M is poorly conditioned	or singular, texture
  coordinates generated	by the resulting function may be inaccurate or
  undefined.

  Note that the	values in params define	a reference plane in eye coordinates.
  The modelview	matrix that is applied to them may not be the same one in
  effect when the polygon vertices are transformed. This function establishes
  a field of texture coordinates that can produce dynamic contour lines	on
  moving objects.

  If pname is GL_SPHERE_MAP and coord is either GL_S or GL_T, s and t
  texture coordinates are generated as follows.	Let u be the unit vector
  pointing from	the origin to the polygon vertex (in eye coordinates). Let n'
  be the current normal, after transformation to eye coordinates. Let
                  T
  f = (f   f   f )  be the reflection vector such that
    x   y	z

                                             T
                f = u - 2n'n' u

              _____________
             | 2  2       2
  Finally, let m = 2\|f +f +(f +1) . Then the values assigned to the s and
                       x  y   z
  t texture coordinates	are

                       f
                        x   1
                  s = --- + -
                       m    2

                       f
                        y   1
                  t = --- + -
                       m    2

  A texture-coordinate generation function is enabled or disabled using
  glEnable or glDisable	with one of the	symbolic texture-coordinate names
  (GL_TEXTURE_GEN_S, GL_TEXTURE_GEN_T, GL_TEXTURE_GEN_R, or GL_TEXTURE_GEN_Q)
  as the argument. When	enabled, the specified texture coordinate is computed
  according to the generating function associated with that coordinate.	When
  disabled, subsequent vertices	take the specified texture coordinate from
  the current set of texture coordinates. Initially, all texture generation
  functions are	set to GL_EYE_LINEAR and are disabled.	Both s plane
  equations are	(1,0,0,0), both	t plane	equations are (0,1,0,0), and all r
  and q	plane equations	are (0,0,0,0).

Errors

  GL_INVALID_ENUM is generated when coord or pname is not an accepted defined
  value, or when pname is GL_TEXTURE_GEN_MODE and params is not	an accepted
  defined value.

  GL_INVALID_ENUM is generated when pname is GL_TEXTURE_GEN_MODE, params is
  GL_SPHERE_MAP, and coord is either GL_R or GL_Q.

  GL_INVALID_OPERATION is generated if glTexGen is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGetTexGen
  glIsEnabled with argument GL_TEXTURE_GEN_S
  glIsEnabled with argument GL_TEXTURE_GEN_T
  glIsEnabled with argument GL_TEXTURE_GEN_R
  glIsEnabled with argument GL_TEXTURE_GEN_Q

See Also

  glTexEnv, glTexImage1D, glTexImage2D,	glTexParameter

Introduction | Alphabetic | Specification

Last Edited: Tue, May 23, 1995

AFV


glTexImage1D.3gl

Name

  glTexImage1D - specify a one-dimensional texture image

C Specification

  void glTexImage1D( GLenum target,
             GLint level,
             GLint components,
             GLsizei width,
             GLint border,
             GLenum format,
             GLenum type,
             const GLvoid *pixels )

Parameters

  target      Specifies	the target texture.  Must be GL_TEXTURE_1D.

  level	      Specifies	the level-of-detail number.  Level 0 is	the base
          image level.  Level n is the nth mipmap reduction	image.

  components  Specifies	the number of color components in the texture.	Must
          be 1, 2, 3, or 4.

                                                                  n
  width	      Specifies	the width of the texture image.	 Must be 2 +2(border)
          for some integer n.  The height of the texture image is 1.

  border      Specifies	the width of the border.  Must be either 0 or 1.

  format      Specifies	the format of the pixel	data.  The following symbolic
          values are accepted: GL_COLOR_INDEX, GL_RED, GL_GREEN, GL_BLUE,
          GL_ALPHA,	GL_RGB,	GL_RGBA, GL_LUMINANCE, and
          GL_LUMINANCE_ALPHA.

  type	      Specifies	the data type of the pixel data.  The following
          symbolic values are accepted: GL_UNSIGNED_BYTE, GL_BYTE,
          GL_BITMAP, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT,
          GL_INT, and GL_FLOAT.

  pixels      Specifies	a pointer to the image data in memory.

Description

  Texturing maps a portion of a	specified texture image	onto each graphical
  primitive for	which texturing	is enabled.  One-dimensional texturing is
  enabled and disabled using glEnable and glDisable with argument
  GL_TEXTURE_1D.

  Texture images are defined with glTexImage1D.	 The arguments describe	the
  parameters of	the texture image, such	as width, width	of the border,
  level-of-detail number (see glTexParameter), and number of color components
  provided.  The last three arguments describe the way the image is
  represented in memory, and they are identical	to the pixel formats used for
  glDrawPixels.

  Data is read from pixels as a	sequence of signed or unsigned bytes, shorts,
  or longs, or single-precision	floating-point values, depending on type.
  These	values are grouped into	sets of	one, two, three, or four values,
  depending on format, to form elements. If type is GL_BITMAP, the data	is
  considered as	a string of unsigned bytes (and	format must be
  GL_COLOR_INDEX). Each	data byte is treated as	eight 1-bit elements, with
  bit ordering determined by GL_UNPACK_LSB_FIRST (see glPixelStore).

  format determines the	composition of each element in pixels.	It can assume
  one of nine symbolic values:

  GL_COLOR_INDEX
        Each element is a single value, a color index. It is converted to
        fixed point	(with an unspecified number of zero bits to the	right
        of the binary point), shifted left or right	depending on the
        value and sign of GL_INDEX_SHIFT, and added	to GL_INDEX_OFFSET
        (see glPixelTransfer). The resulting index is converted to a set
        of color components	using the GL_PIXEL_MAP_I_TO_R,
        GL_PIXEL_MAP_I_TO_G, GL_PIXEL_MAP_I_TO_B, and GL_PIXEL_MAP_I_TO_A
        tables, and	clamped	to the range [0,1].

  GL_RED    Each element is a single red component. It is converted to
        floating point and assembled into an RGBA element by attaching
        0.0	for green and blue, and	1.0 for	alpha. Each component is then
        multiplied by the signed scale factor GL_c_SCALE, added to the
        signed bias	GL_c_BIAS, and clamped to the range [0,1] (see
        glPixelTransfer).

  GL_GREEN  Each element is a single green component. It is converted to
        floating point and assembled into an RGBA element by attaching
        0.0	for red	and blue, and 1.0 for alpha. Each component is then
        multiplied by the signed scale factor GL_c_SCALE, added to the
        signed bias	GL_c_BIAS, and clamped to the range [0,1] (see
        glPixelTransfer).

  GL_BLUE   Each element is a single blue component. It	is converted to
        floating point and assembled into an RGBA element by attaching
        0.0	for red	and green, and 1.0 for alpha. Each component is	then
        multiplied by the signed scale factor GL_c_SCALE, added to the
        signed bias	GL_c_BIAS, and clamped to the range [0,1] (see
        glPixelTransfer).

  GL_ALPHA  Each element is a single alpha component. It is converted to
        floating point and assembled into an RGBA element by attaching
        0.0	for red, green,	and blue.  Each	component is then multiplied
        by the signed scale	factor GL_c_SCALE, added to the	signed bias
        GL_c_BIAS, and clamped to the range	[0,1] (see glPixelTransfer).

  GL_RGB    Each element is an RGB triple.  It is converted to floating	point
        and	assembled into an RGBA element by attaching 1.0	for alpha.
        Each component is then multiplied by the signed scale factor
        GL_c_SCALE,	added to the signed bias GL_c_BIAS, and	clamped	to
        the	range [0,1] (see  glPixelTransfer).

  GL_RGBA   Each element is a complete RGBA element.  It is converted to
        floating point.  Each component is then multiplied by the signed
        scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and
        clamped to the range [0,1] (see glPixelTransfer).

  GL_LUMINANCE
        Each element is a single luminance value.  It is converted to
        floating point, then assembled into	an RGBA	element	by
        replicating	the luminance value three times	for red, green,	and
        blue and attaching 1.0 for alpha. Each component is	then
        multiplied by the signed scale factor GL_c_SCALE, added to the
        signed bias	GL_c_BIAS, and clamped to the range [0,1] (see
        glPixelTransfer).

  GL_LUMINANCE_ALPHA
        Each element is a luminance/alpha pair.  It	is converted to
        floating point, then assembled into	an RGBA	element	by
        replicating	the luminance value three times	for red, green,	and
        blue.  Each	component is then multiplied by	the signed scale
        factor GL_c_SCALE, added to	the signed bias	GL_c_BIAS, and
        clamped to the range [0,1] (see glPixelTransfer).

  A texture image can have up to four components per texture element,
  depending on components.  A one-component texture image uses only the	red
  component of the RGBA	color extracted	from pixels. A two-component image
  uses the R and A values.  A three-component image uses the R,	G, and B
  values.  A four-component image uses all of the RGBA components.

Notes

  Texturing has	no effect in color index mode.

  The texture image can	be represented by the same data	formats	as the pixels
  in a glDrawPixels command, except that GL_STENCIL_INDEX and
  GL_DEPTH_COMPONENT cannot be used.  glPixelStore and glPixelTransfer modes
  affect texture images	in exactly the way they	affect glDrawPixels.

  A texture image with zero width indicates the	null texture.  If the null
  texture is specified for level-of-detail 0, it is as if texturing were
  disabled.

Errors

  GL_INVALID_ENUM is generated when target is not GL_TEXTURE_1D.

  GL_INVALID_ENUM is generated when format is not an accepted format
  constant.  Format constants other than GL_STENCIL_INDEX and
  GL_DEPTH_COMPONENT are accepted.

  GL_INVALID_ENUM is generated when type is not	a type constant.

  GL_INVALID_ENUM is generated if type is GL_BITMAP and	format is not
  GL_COLOR_INDEX.

  GL_INVALID_VALUE is generated	if level is less than zero or greater than
  log max, where max is	the returned value of GL_MAX_TEXTURE_SIZE.
     2
  GL_INVALID_VALUE is generated	if components is not 1, 2, 3, or 4.

  GL_INVALID_VALUE is generated	if width is less than zero or greater than
                                                              n
  2 + GL_MAX_TEXTURE_SIZE, or if it cannot be represented as 2 +2(border) for
  some integer value of	n.

  GL_INVALID_VALUE is generated	if border is not 0 or 1.

  GL_INVALID_OPERATION is generated if glTexImage1D is executed	between	the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGetTexImage
  glIsEnabled with argument GL_TEXTURE_1D

See Also

  glDrawPixels,	glFog, glPixelStore, glPixelTransfer, glTexEnv,	glTexGen,
  glTexImage2D,	glTexParameter

Introduction | Alphabetic | Specification

Last Edited: Tue, May 23, 1995

AFV


glTexImage2D.3gl

Name

  glTexImage2D - specify a two-dimensional texture image

C Specification

  void glTexImage2D( GLenum target,
             GLint level,
             GLint components,
             GLsizei width,
             GLsizei height,
             GLint border,
             GLenum format,
             GLenum type,
             const GLvoid *pixels )

Parameters

  target      Specifies	the target texture.  Must be GL_TEXTURE_2D.

  level	      Specifies	the level-of-detail number.  Level 0 is	the base
          image level.  Level n is the nth mipmap reduction	image.

  components  Specifies	the number of color components in the texture.	Must
          be 1, 2, 3, or 4.

                                                                  n
  width	      Specifies the width of the texture image.  Must be 2 +2(border)
              for some integer n.

  height      Specifies	the height of the texture image.  Must be
           m
          2 +2(border) for some integer m.

  border      Specifies	the width of the border.  Must be either 0 or 1.

  format      Specifies	the format of the pixel	data.  The following symbolic
          values are accepted: GL_COLOR_INDEX, GL_RED, GL_GREEN, GL_BLUE,
          GL_ALPHA,	GL_RGB,	GL_RGBA, GL_LUMINANCE, and
          GL_LUMINANCE_ALPHA.

  type	      Specifies	the data type of the pixel data.  The following
          symbolic values are accepted: GL_UNSIGNED_BYTE, GL_BYTE,
          GL_BITMAP, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT,
          GL_INT, and GL_FLOAT.

  pixels      Specifies	a pointer to the image data in memory.

Description

  Texturing maps a portion of a	specified texture image	onto each graphical
  primitive for	which texturing	is enabled.  Two-dimensional texturing is
  enabled and disabled using glEnable and glDisable with argument
  GL_TEXTURE_2D.

  Texture images are defined with glTexImage2D.	The arguments describe the
  parameters of	the texture image, such	as height, width, width	of the
  border, level-of-detail number (see glTexParameter), and number of color
  components provided.	The last three arguments describe the way the image
  is represented in memory, and	they are identical to the pixel	formats	used
  for glDrawPixels.

  Data is read from pixels as a	sequence of signed or unsigned bytes, shorts,
  or longs, or single-precision	floating-point values, depending on type.
  These	values are grouped into	sets of	one, two, three, or four values,
  depending on format, to form elements. If type is GL_BITMAP, the data	is
  considered as	a string of unsigned bytes (and	format must be
  GL_COLOR_INDEX). Each	data byte is treated as	eight 1-bit elements, with
  bit ordering determined by GL_UNPACK_LSB_FIRST (see glPixelStore).

  format determines the	composition of each element in pixels.	It can assume
  one of nine symbolic values:

  GL_COLOR_INDEX
        Each element is a single value, a color index. It is converted to
        fixed point	(with an unspecified number of zero bits to the	right
        of the binary point), shifted left or right	depending on the
        value and sign of GL_INDEX_SHIFT, and added	to GL_INDEX_OFFSET
        (see glPixelTransfer). The resulting index is converted to a set
        of color components	using the GL_PIXEL_MAP_I_TO_R,
        GL_PIXEL_MAP_I_TO_G, GL_PIXEL_MAP_I_TO_B, and GL_PIXEL_MAP_I_TO_A
        tables, and	clamped	to the range [0,1].

  GL_RED    Each element is a single red component. It is converted to
        floating point and assembled into an RGBA element by attaching
        0.0	for green and blue, and	1.0 for	alpha. Each component is then
        multiplied by the signed scale factor GL_c_SCALE, added to the
        signed bias	GL_c_BIAS, and clamped to the range [0,1] (see
        glPixelTransfer).

  GL_GREEN  Each element is a single green component. It is converted to
        floating point and assembled into an RGBA element by attaching
        0.0	for red	and blue, and 1.0 for alpha. Each component is then
        multiplied by the signed scale factor GL_c_SCALE, added to the
        signed bias	GL_c_BIAS, and clamped to the range [0,1] (see
        glPixelTransfer).

  GL_BLUE   Each element is a single blue component. It	is converted to
        floating point and assembled into an RGBA element by attaching
        0.0	for red	and green, and 1.0 for alpha. Each component is	then
        multiplied by the signed scale factor GL_c_SCALE, added to the
        signed bias	GL_c_BIAS, and clamped to the range [0,1] (see
        glPixelTransfer).

  GL_ALPHA  Each element is a single alpha component. It is converted to
        floating point and assembled into an RGBA element by attaching
        0.0	for red, green,	and blue.  Each	component is then multiplied
        by the signed scale	factor GL_c_SCALE, added to the	signed bias
        GL_c_BIAS, and clamped to the range	[0,1] (see glPixelTransfer).

  GL_RGB    Each element is an RGB triple.  It is converted to floating	point
        and	assembled into an RGBA element by attaching 1.0	for alpha.
        Each component is then multiplied by the signed scale factor
        GL_c_SCALE,	added to the signed bias GL_c_BIAS, and	clamped	to
        the	range [0,1] (see  glPixelTransfer).

  GL_RGBA   Each element is a complete RGBA element.  It is converted to
        floating point.  Each component is then multiplied by the signed
        scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and
        clamped to the range [0,1] (see glPixelTransfer).

  GL_LUMINANCE
        Each element is a single luminance value.  It is converted to
        floating point, then assembled into	an RGBA	element	by
        replicating	the luminance value three times	for red, green,	and
        blue and attaching 1.0 for alpha. Each component is	then
        multiplied by the signed scale factor GL_c_SCALE, added to the
        signed bias	GL_c_BIAS, and clamped to the range [0,1] (see
        glPixelTransfer).

  GL_LUMINANCE_ALPHA
        Each element is a luminance/alpha pair.  It	is converted to
        floating point, then assembled into	an RGBA	element	by
        replicating	the luminance value three times	for red, green,	and
        blue.  Each	component is then multiplied by	the signed scale
        factor GL_c_SCALE, added to	the signed bias	GL_c_BIAS, and
        clamped to the range [0,1] (see glPixelTransfer).

  Please refer to the glDrawPixels reference page for a	description of the
  acceptable values for	the type parameter.  A texture image can have up to
  four components per texture element, depending on components.	 A one-
  component texture image uses only the	red component of the RGBA color
  extracted from pixels. A two-component image uses the	R and A	values.	 A
  three-component image	uses the R, G, and B values.  A	four-component image
  uses all of the RGBA components.

Notes

  Texturing has	no effect in color index mode.

  The texture image can	be represented by the same data	formats	as the pixels
  in a glDrawPixels command, except that GL_STENCIL_INDEX and
  GL_DEPTH_COMPONENT cannot be used.  glPixelStore and glPixelTransfer modes
  affect texture images	in exactly the way they	affect glDrawPixels.

  A texture image with zero height or width indicates the null texture.	 If
  the null texture is specified	for level-of-detail 0, it is as	if texturing
  were disabled.

Errors

  GL_INVALID_ENUM is generated when target is not GL_TEXTURE_2D.

  GL_INVALID_ENUM is generated when format is not an accepted format
  constant.  Format constants other than GL_STENCIL_INDEX and
  GL_DEPTH_COMPONENT are accepted.

  GL_INVALID_ENUM is generated when type is not	a type constant.

  GL_INVALID_ENUM is generated if type is GL_BITMAP and	format is not
  GL_COLOR_INDEX.

  GL_INVALID_VALUE is generated	if level is less than zero or greater than
  log max, where max is	the returned value of GL_MAX_TEXTURE_SIZE.
     2
  GL_INVALID_VALUE is generated	if components is not 1,	2, 3, or 4.

  GL_INVALID_VALUE is generated	if width or height is less than	zero or
  greater than 2 + GL_MAX_TEXTURE_SIZE,	or if either cannot be represented as
   k
  2 +2(border) for some	integer	value of k.

  GL_INVALID_VALUE is generated	if border is not 0 or 1.

  GL_INVALID_OPERATION is generated if glTexImage2D is executed	between	the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGetTexImage
  glIsEnabled with argument GL_TEXTURE_2D

See Also

  glDrawPixels,	glFog, glPixelStore, glPixelTransfer, glTexEnv,	glTexGen,
  glTexImage1D,	glTexParameter

Introduction | Alphabetic | Specification

Last Edited: Tue, May 23, 1995

AFV


glTexParameter.3gl

Name

  glTexParameterf, glTexParameteri, glTexParameterfv, glTexParameteriv - set
  texture parameters

C Specification

  void glTexParameterf(	GLenum target,
            GLenum pname,
            GLfloat	param )
  void glTexParameteri(	GLenum target,
            GLenum pname,
            GLint param )

Parameters

  target  Specifies the	target texture,	which must be either GL_TEXTURE_1D or
      GL_TEXTURE_2D.

  pname	  Specifies the	symbolic name of a single-valued texture parameter.
      pname	can be one of the following: GL_TEXTURE_MIN_FILTER,
      GL_TEXTURE_MAG_FILTER, GL_TEXTURE_WRAP_S, or GL_TEXTURE_WRAP_T.

  param	  Specifies the	value of pname.

C Specification

  void glTexParameterfv( GLenum	target,
             GLenum	pname,
             const GLfloat *params )
  void glTexParameteriv( GLenum	target,
             GLenum	pname,
             const GLint *params )

Parameters

  target
       Specifies the target texture, which must	be either GL_TEXTURE_1D	or
       GL_TEXTURE_2D.

  pname
       Specifies the symbolic name of a	texture	parameter.  pname can be one
       of the following: GL_TEXTURE_MIN_FILTER,	GL_TEXTURE_MAG_FILTER,
       GL_TEXTURE_WRAP_S, GL_TEXTURE_WRAP_T, or	GL_TEXTURE_BORDER_COLOR.

  params
       Specifies a pointer to an array where the value or values of pname are
       stored.

Description

  Texture mapping is a technique that applies an image onto an object's
  surface as if	the image were a decal or cellophane shrink-wrap. The image
  is created in	texture	space, with an (s, t) coordinate system. A texture is
  a one- or two-dimensional image and a	set of parameters that determine how
  samples are derived from the image.

  glTexParameter assigns the value or values in	params to the texture
  parameter specified as pname.	target defines the target texture, either
  GL_TEXTURE_1D	or GL_TEXTURE_2D.  The following symbols are accepted in
  pname:

  GL_TEXTURE_MIN_FILTER
        The	texture	minifying function is used whenever the	pixel being
        textured maps to an	area greater than one texture element. There
        are	six defined minifying functions.  Two of them use the nearest
        one	or nearest four	texture	elements to compute the	texture
        value. The other four use mipmaps.

        A mipmap is	an ordered set of arrays representing the same image
        at progressively lower resolutions.	If the texture has dimensions
        2nx2m there	are max(n,m)+1 mipmaps.	The first mipmap is the
        original texture, with dimensions 2nx2m. Each subsequent mipmap
        has	dimensions 2k-1x2l-1 where 2kx2l are the dimensions of the
        previous mipmap, until either k=0 or l=0.  At that point,
        subsequent mipmaps have dimension 1x2l-1 or	2k-1x1 until the
        final mipmap, which	has dimension 1x1. Mipmaps are defined using
        glTexImage1D or glTexImage2D with the level-of-detail argument
        indicating the order of the	mipmaps.  Level	0 is the original
        texture; level max(n,m) is the final 1x1 mipmap.

        params supplies a function for minifying the texture as one	of
        the	following:

        GL_NEAREST
              Returns the value	of the texture element that is
              nearest (in Manhattan distance) to the center of the
              pixel being textured.

        GL_LINEAR Returns the weighted average of the four texture
              elements that are	closest	to the center of the pixel
              being textured.  These can include border	texture
              elements,	depending on the values	of GL_TEXTURE_WRAP_S
              and GL_TEXTURE_WRAP_T, and on the	exact mapping.

        GL_NEAREST_MIPMAP_NEAREST
              Chooses the mipmap that most closely matches the size
              of the pixel being textured and uses the GL_NEAREST
              criterion	(the texture element nearest to	the center of
              the pixel) to produce a texture value.

        GL_LINEAR_MIPMAP_NEAREST
              Chooses the mipmap that most closely matches the size
              of the pixel being textured and uses the GL_LINEAR
              criterion	(a weighted average of the four	texture
              elements that are	closest	to the center of the pixel)
              to produce a texture value.

        GL_NEAREST_MIPMAP_LINEAR
              Chooses the two mipmaps that most	closely	match the
              size of the pixel	being textured and uses	the
              GL_NEAREST criterion (the	texture	element	nearest	to
              the center of the	pixel) to produce a texture value
              from each	mipmap.	The final texture value	is a weighted
              average of those two values.

        GL_LINEAR_MIPMAP_LINEAR
              Chooses the two mipmaps that most	closely	match the
              size of the pixel	being textured and uses	the GL_LINEAR
              criterion	(a weighted average of the four	texture
              elements that are	closest	to the center of the pixel)
              to produce a texture value from each mipmap.  The	final
              texture value is a weighted average of those two
              values.

        As more texture elements are sampled in the	minification process,
        fewer aliasing artifacts will be apparent. While the GL_NEAREST
        and	GL_LINEAR minification functions can be	faster than the	other
        four, they sample only one or four texture elements	to determine
        the	texture	value of the pixel being rendered and can produce
        moire patterns or ragged transitions. The default value of
        GL_TEXTURE_MIN_FILTER is GL_NEAREST_MIPMAP_LINEAR.

  GL_TEXTURE_MAG_FILTER
        The	texture	magnification function is used when the	pixel being
        textured maps to an	area less than or equal	to one texture
        element.  It sets the texture magnification	function to either of
        the	following:

        GL_NEAREST
              Returns the value	of the texture element that is
              nearest (in Manhattan distance) to the center of the
              pixel being textured.

              GL_LINEAR	Returns	the weighted average of	the four
              texture elements that are	closest	to the center of the
              pixel being textured.  These can include border texture
              elements,	depending on the values	of GL_TEXTURE_WRAP_S
              and GL_TEXTURE_WRAP_T, and on the	exact mapping.

        GL_NEAREST is generally faster than	GL_LINEAR, but it can produce
        textured images with sharper edges because the transition between
        texture elements is	not as smooth. The default value of
        GL_TEXTURE_MAG_FILTER is GL_LINEAR.

  GL_TEXTURE_WRAP_S
        Sets the wrap parameter for	texture	coordinate s to	either
        GL_CLAMP or	GL_REPEAT.  GL_CLAMP causes s coordinates to be
        clamped to the range [0,1] and is useful for preventing wrapping
        artifacts when mapping a single image onto an object. GL_REPEAT
        causes the integer part of the s coordinate	to be ignored; the GL
        uses only the fractional part, thereby creating a repeating
        pattern. Border texture elements are accessed only if wrapping is
        set	to GL_CLAMP.  Initially, GL_TEXTURE_WRAP_S is set to
        GL_REPEAT.

  GL_TEXTURE_WRAP_T
        Sets the wrap parameter for	texture	coordinate t to	either
        GL_CLAMP or	GL_REPEAT.  See	the discussion under
        GL_TEXTURE_WRAP_S. Initially, GL_TEXTURE_WRAP_T is set to
        GL_REPEAT.

  GL_TEXTURE_BORDER_COLOR
        Sets a border color.  params contains four values that comprise
        the	RGBA color of the texture border. Integer color	components
        are	interpreted linearly such that the most	positive integer maps
        to 1.0, and	the most negative integer maps to -1.0.	 The values
        are	clamped	to the range [0,1] when	they are specified.
        Initially, the border color	is (0, 0, 0, 0).

Notes

  Suppose texturing is enabled (by calling glEnable with argument
  GL_TEXTURE_1D	or GL_TEXTURE_2D) and GL_TEXTURE_MIN_FILTER is set to one of
  the functions	that requires a	mipmap.	 If either the dimensions of the
  texture images currently defined (with previous calls	to glTexImage1D	or
  glTexImage2D)	do not follow the proper sequence for mipmaps (described
  above), or there are fewer texture images defined than are needed, or	the
  set of texture images	have differing numbers of texture components, then it
  is as	if texture mapping were	disabled.

  Linear filtering accesses the	four nearest texture elements only in 2-D
  textures.  In	1-D textures, linear filtering accesses	the two	nearest
  texture elements.

Errors

  GL_INVALID_ENUM is generated when target or pname is not one of the
  accepted defined values, or when params should have a	defined	constant
  value	(based on the value of pname) and does not.

  GL_INVALID_OPERATION is generated if glTexParameter is called	between	a
  call to glBegin and the corresponding	call to	glEnd.

Associated Gets

  glGetTexParameter
  glGetTexLevelParameter

See Also

  glTexEnv, glTexImage1D, glTexImage2D,	glTexGen

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glTranslate.3gl

Name

  glTranslated,	glTranslatef - multiply	the current matrix by a	translation
  matrix

C Specification

  void glTranslated( GLdouble x,
             GLdouble y,
             GLdouble z	)
  void glTranslatef( GLfloat x,
             GLfloat y,
             GLfloat z )

Parameters

  x, y,	z
     Specify the x,	y, and z coordinates of	a translation vector.

Description

  glTranslate moves the	coordinate system origin to the	point specified	by
  (x,y,z).  The translation vector is used to compute a	4x4 translation
  matrix:

                | 1	   0	0    x	 |
                |			 |
                | 0	   1	0    y	 |
                |			 |
                | 0	   0	1    z	 |
                |			 |
                | 0	   0	0    1	 |

  The current matrix (see glMatrixMode)	is multiplied by this translation
  matrix, with the product replacing the current matrix.  That is, if M	is
  the current matrix and T is the translation matrix, then M is	replaced with
  M · T.

  If the matrix	mode is	either GL_MODELVIEW or GL_PROJECTION, all objects
  drawn	after glTranslate is called are	translated.  Use glPushMatrix and
  glPopMatrix to save and restore the untranslated coordinate system.

Errors

  GL_INVALID_OPERATION is generated if glTranslate is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_MATRIX_MODE
  glGet	with argument GL_MODELVIEW_MATRIX
  glGet	with argument GL_PROJECTION_MATRIX
  glGet	with argument GL_TEXTURE_MATRIX

See Also

  glMatrixMode,	glMultMatrix, glPushMatrix, glRotate, glScale

Introduction | Alphabetic | Specification

Last Edited: Tue, May 23, 1995

AFV


gluBeginCurve.3gl

Name

  gluBeginCurve, gluEndCurve - delimit a NURBS curve definition

C Specification

  void gluBeginCurve( GLUnurbsObj *nobj	)

  void gluEndCurve( GLUnurbsObj	*nobj )

Parameters

  nobj	Specifies the NURBS object (created with gluNewNurbsRenderer).

Description

  Use gluBeginCurve to mark the	beginning of a NURBS curve definition.	After
  calling gluBeginCurve, make one or more calls	to gluNurbsCurve to define
  the attributes of the	curve.	Exactly	one of the calls to gluNurbsCurve
  must have a curve type of GL_MAP1_VERTEX_3 or	GL_MAP1_VERTEX_4.  To mark
  the end of the NURBS curve definition, call gluEndCurve.

  OpenGL evaluators are	used to	render the NURBS curve as a series of line
  segments.  Evaluator state is	preserved during rendering with
  glPushAttrib(GL_EVAL_BIT) and	glPopAttrib(). See the glPushAttrib reference
  page for details on exactly what state these calls preserve.

Example

  The following	commands render	a textured NURBS curve with normals; texture
  coordinates and normals are also specified as	NURBS curves:

  gluBeginCurve(nobj);
     gluNurbsCurve(nobj, ..., GL_MAP1_TEXTURE_COORD_2);
     gluNurbsCurve(nobj, ..., GL_MAP1_NORMAL);
     gluNurbsCurve(nobj, ..., GL_MAP1_VERTEX_4); gluEndCurve(nobj);

See Also

  gluBeginSurface, gluBeginTrim, gluNewNurbsRenderer, gluNurbsCurve,
  glPopAttrib, glPushAttrib

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluBeginPolygon.3gl

gluBeginPolygon.3gl (Obsolete in GLU version 1.2 and later)

Name

  gluBeginPolygon, gluEndPolygon - delimit a polygon description

C Specification

  void gluBeginPolygon(	GLUtesselator *tess )

  void gluEndPolygon( GLUtesselator *tess )

Parameters

  tess	Specifies the tessellation object (created with	gluNewTess).

Description

  gluBeginPolygon and gluEndPolygon delimit the	definition of a	nonconvex
  polygon.  To define such a polygon, first call gluBeginPolygon.  Then
  define the contours of the polygon by	calling	gluTessVertex for each vertex
  and gluNextContour to	start each new contour.	 Finally, call gluEndPolygon
  to signal the	end of the definition.	See the	gluTessVertex and
  gluNextContour reference pages for more details.

  Once gluEndPolygon is	called,	the polygon is tessellated, and	the resulting
  triangles are	described through callbacks.  See gluTessCallback for
  descriptions of the callback functions.

Notes

  As of GLU version 1.2, this command is obsolete and is provided for backwards
  compatibility only. GLU version 1.2 (and later) can be distinguished during
  compilation by checking if the pre-processor symbol GLU_VERSION_1_2 is defined.

  Calls to gluBeginPolygon are mapped to gluTessBeginPolygon followed by
  gluTessBeginContour. Calls to gluEndPolygon are mapped to gluTessEndContour
  followed by gluTessEndPolygon.

Example

  A quadrilateral with a triangular hole in it can be described	like this:

  gluBeginPolygon(tobj);
     gluTessVertex(tobj, v1, v1);
     gluTessVertex(tobj, v2, v2);
     gluTessVertex(tobj, v3, v3);
     gluTessVertex(tobj, v4, v4);
  gluNextContour(tobj, GLU_INTERIOR);
     gluTessVertex(tobj, v5, v5);
     gluTessVertex(tobj, v6, v6);
     gluTessVertex(tobj, v7, v7);
  gluEndPolygon(tobj);

See Also

  gluNewTess, gluNextContour, gluTessCallback, gluTessVertex,
  gluTessBeginPolygon, gluTessBeginContour

Introduction | Alphabetic | Specification

Last Edited: Thu, May 25, 1995

AFV


gluBeginPolygon.3gl

gluBeginPolygon.3gl (GLU versions 1.0 and 1.1)

Name

  gluBeginPolygon, gluEndPolygon - delimit a polygon description

C Specification

  void gluBeginPolygon(	GLUtriangulatorObj *tobj )

  void gluEndPolygon( GLUtriangulatorObj *tobj )

Parameters

  tobj	Specifies the tessellation object (created with	gluNewTess).

Description

  gluBeginPolygon and gluEndPolygon delimit the	definition of a	nonconvex
  polygon.  To define such a polygon, first call gluBeginPolygon.  Then
  define the contours of the polygon by	calling	gluTessVertex for each vertex
  and gluNextContour to	start each new contour.	 Finally, call gluEndPolygon
  to signal the	end of the definition.	See the	gluTessVertex and
  gluNextContour reference pages for more details.

  Once gluEndPolygon is	called,	the polygon is tessellated, and	the resulting
  triangles are	described through callbacks.  See gluTessCallback for
  descriptions of the callback functions.

Example

  A quadrilateral with a triangular hole in it can be described	like this:

  gluBeginPolygon(tobj);
     gluTessVertex(tobj, v1, v1);
     gluTessVertex(tobj, v2, v2);
     gluTessVertex(tobj, v3, v3);
     gluTessVertex(tobj, v4, v4);
  gluNextContour(tobj, GLU_INTERIOR);
     gluTessVertex(tobj, v5, v5);
     gluTessVertex(tobj, v6, v6);
     gluTessVertex(tobj, v7, v7);
  gluEndPolygon(tobj);

See Also

  gluNewTess, gluNextContour, gluTessCallback, gluTessVertex

Introduction | Alphabetic | Specification

Last Edited: Fri, May 26, 1995

AFV


gluBeginSurface.3gl

Name

  gluBeginSurface, gluEndSurface - delimit a NURBS surface definition

C Specification

  void gluBeginSurface(	GLUnurbsObj *nobj )

  void gluEndSurface( GLUnurbsObj *nobj	)

Parameters

  nobj	Specifies the NURBS object (created with gluNewNurbsRenderer).

Description

  Use gluBeginSurface to mark the beginning of a NURBS surface definition.
  After	calling	gluBeginSurface, make one or more calls	to gluNurbsSurface to
  define the attributes	of the surface.	 Exactly one of	these calls to
  gluNurbsSurface must have a surface type of GL_MAP2_VERTEX_3 or
  GL_MAP2_VERTEX_4.  To	mark the end of	the NURBS surface definition, call
  gluEndSurface.

  Trimming of NURBS surfaces is	supported with gluBeginTrim, gluPwlCurve,
  gluNurbsCurve, and gluEndTrim.  Refer	to the gluBeginTrim reference page
  for details.

  OpenGL evaluators are	used to	render the NURBS surface as a set of
  polygons.  Evaluator state is	preserved during rendering with
  glPushAttrib(GL_EVAL_BIT) and	glPopAttrib(). See the glPushAttrib reference
  page for details on exactly what state these calls preserve.

Example

  The following	commands render	a textured NURBS surface with normals; the
  texture coordinates and normals are also described as	NURBS surfaces:

  gluBeginSurface(nobj);
     gluNurbsSurface(nobj, ...,	GL_MAP2_TEXTURE_COORD_2);
     gluNurbsSurface(nobj, ...,	GL_MAP2_NORMAL);
     gluNurbsSurface(nobj, ...,	GL_MAP2_VERTEX_4); gluEndSurface(nobj);

See Also

  gluBeginCurve, gluBeginTrim, gluNewNurbsRenderer, gluNurbsCurve,
  gluNurbsSurface, gluPwlCurve

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluBeginTrim.3gl

Name

  gluBeginTrim,	gluEndTrim - delimit a NURBS trimming loop definition

C Specification

  void gluBeginTrim( GLUnurbsObj *nobj )

  void gluEndTrim( GLUnurbsObj *nobj )

Parameters

  nobj	Specifies the NURBS object (created with gluNewNurbsRenderer).

Description

  Use gluBeginTrim to mark the beginning of a trimming loop, and gluEndTrim
  to mark the end of a trimming	loop.  A trimming loop is a set	of oriented
  curve	segments (forming a closed curve) that define boundaries of a NURBS
  surface.  You	include	these trimming loops in	the definition of a NURBS
  surface, between calls to gluBeginSurface and	gluEndSurface.

  The definition for a NURBS surface can contain many trimming loops.  For
  example, if you wrote	a definition for a NURBS surface that resembled	a
  rectangle with a hole	punched	out, the definition would contain two
  trimming loops.  One loop would define the outer edge	of the rectangle;
  the other would define the hole punched out of the rectangle.	 The
  definitions of each of these trimming	loops would be bracketed by a
  gluBeginTrim/gluEndTrim pair.

  The definition of a single closed trimming loop can consist of multiple
  curve	segments, each described as a piecewise	linear curve (see
  gluPwlCurve) or as a single NURBS curve (see gluNurbsCurve), or as a
  combination of both in any order.  The only library calls that can appear
  in a trimming	loop definition	(between the calls to gluBeginTrim and
  gluEndTrim) are gluPwlCurve and gluNurbsCurve.

  The area of the NURBS	surface	that is	displayed is the region	in the domain
  to the left of the trimming curve as the curve parameter increases.  Thus,
  the retained region of the NURBS surface is inside a counterclockwise
  trimming loop	and outside a clockwise	trimming loop.	For the	rectangle
  mentioned earlier, the trimming loop for the outer edge of the rectangle
  runs counterclockwise, while the trimming loop for the punched-out hole
  runs clockwise.

  If you use more than one curve to define a single trimming loop, the curve
  segments must	form a closed loop (that is, the endpoint of each curve	must
  be the starting point	of the next curve, and the endpoint of the final
  curve	must be	the starting point of the first	curve).	 If the	endpoints of
  the curve are	sufficiently close together but	not exactly coincident,	they
  will be coerced to match.  If	the endpoints are not sufficiently close, an
  error	results	(see gluNurbsCallback).

  If a trimming	loop definition	contains multiple curves, the direction	of
  the curves must be consistent	(that is, the inside must be to	the left of
  all of the curves).  Nested trimming loops are legal as long as the curve
  orientations alternate correctly.  Trimming curves cannot be self-
  intersecting,	nor can	they intersect one another (or an error	results).

  If no	trimming information is	given for a NURBS surface, the entire surface
  is drawn.

Example

  This code fragment defines a trimming	loop that consists of one piecewise
  linear curve,	and two	NURBS curves:

  gluBeginTrim(nobj);
     gluPwlCurve(..., GLU_MAP1_TRIM_2);
     gluNurbsCurve(...,	GLU_MAP1_TRIM_2);
     gluNurbsCurve(...,	GLU_MAP1_TRIM_3); gluEndTrim(nobj);

See Also

  gluBeginSurface, gluNewNurbsRenderer,	gluNurbsCallback, gluNurbsCurve,
  gluPwlCurve

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluBuild1DMipmaps.3gl

Name

  gluBuild1DMipmaps - create 1-D mipmaps

C Specification

  int gluBuild1DMipmaps( GLenum	target,
             GLint components,
             GLint width,
             GLenum	format,
             GLenum	type,
             const void *data )

Parameters

  target      Specifies	the target texture. Must be GL_TEXTURE_1D.

  components  Specifies	the number of color components in the texture.	Must
          be 1, 2, 3, or 4.

  width	      Specifies	the width of the texture image.

  format      Specifies	the format of the pixel	data.  Must be one of
          GL_COLOR_INDEX, GL_RED, GL_GREEN,	GL_BLUE, GL_ALPHA, GL_RGB,
          GL_RGBA, GL_LUMINANCE, and GL_LUMINANCE_ALPHA.

  type	      Specifies	the data type for data.	 Must be one of
          GL_UNSIGNED_BYTE,	GL_BYTE, GL_BITMAP, GL_UNSIGNED_SHORT,
          GL_SHORT,	GL_UNSIGNED_INT, GL_INT, or GL_FLOAT.

  data	      Specifies	a pointer to the image data in memory.

Description

  gluBuild1DMipmaps obtains the	input image and	generates all mipmap images
  (using gluScaleImage)	so that	the input image	can be used as a mipmapped
  texture image.  glTexImage1D is then called to load each of the images.  If
  the width of the input image is not a	power of two, then the image is
  scaled to the	nearest	power of two before the	mipmaps	are generated.

  A return value of zero indicates success. Otherwise, a GLU error code	is
  returned (see	gluErrorString).

  Please refer to the glTexImage1D reference page for a	description of the
  acceptable values for	the format parameter. See the glDrawPixels reference
  page for a description of the	acceptable values for the type parameter.

See Also

  glTexImage1D,	gluBuild2DMipmaps, gluErrorString, gluScaleImage

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluBuild2DMipmaps.3gl

Name

  gluBuild2DMipmaps - create 2-D mipmaps

C Specification

  int gluBuild2DMipmaps( GLenum	target,
             GLint components,
             GLint width,
             GLint height,
             GLenum	format,
             GLenum	type,
             const void *data )

Parameters

  target      Specifies	the target texture.  Must be GL_TEXTURE_2D.

  components  Specifies	the number of color components in the texture.	Must
          be 1, 2, 3, or 4.

  width, height
          Specifies	the width and height, respectively, of the texture
          image.

  format      Specifies	the format of the pixel	data.  Must be one of:
          GL_COLOR_INDEX, GL_RED, GL_GREEN,	GL_BLUE, GL_ALPHA, GL_RGB,
          GL_RGBA, GL_LUMINANCE, and GL_LUMINANCE_ALPHA.

  type	      Specifies	the data type for data.	 Must be one of:
          GL_UNSIGNED_BYTE,	GL_BYTE, GL_BITMAP, GL_UNSIGNED_SHORT,
          GL_SHORT,	GL_UNSIGNED_INT, GL_INT, or GL_FLOAT.

  data	      Specifies	a pointer to the image data in memory.

Description

  gluBuild2DMipmaps obtains the	input image and	generates all mipmap images
  (using gluScaleImage)	so that	the input image	can be used as a mipmapped
  texture image.  glTexImage2D is then called to load each of the images.  If
  the dimensions of the	input image are	not powers of two, then	the image is
  scaled so that both the width	and height are powers of two before the
  mipmaps are generated.

  A return value of 0 indicates	success. Otherwise, a GLU error	code is
  returned (see	gluErrorString).

  Please refer to the glTexImage1D reference page for a	description of the
  acceptable values for	the format parameter. See the glDrawPixels reference
  page for a description of the	acceptable values for the type parameter.

See Also

  glDrawPixels,	glTexImage1D, glTexImage2D, gluBuild1DMipmaps,
  gluErrorString, gluScaleImage

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluCylinder.3gl

Name

  gluCylinder -	draw a cylinder

C Specification

  void gluCylinder( GLUquadricObj *qobj,
            GLdouble baseRadius,
            GLdouble topRadius,
            GLdouble height,
            GLint slices,
            GLint stacks )

Parameters

  qobj	      Specifies	the quadrics object (created with gluNewQuadric).

  baseRadius  Specifies	the radius of the cylinder at z	= 0.

  topRadius   Specifies	the radius of the cylinder at z	= height.

  height      Specifies	the height of the cylinder.

  slices      Specifies	the number of subdivisions around the z	axis.

  stacks      Specifies	the number of subdivisions along the z axis.

Description

  gluCylinder draws a cylinder oriented	along the z axis.  The base of the
  cylinder is placed at	 z = 0,	and the	top at z=height.  Like a sphere, a
  cylinder is subdivided around	the z axis into	slices,	and along the z	axis
  into stacks.

  Note that if topRadius is set	to zero, then this routine will	generate a
  cone.

  If the orientation is	set to GLU_OUTSIDE (with gluQuadricOrientation), then
  any generated	normals	point away from	the z axis.  Otherwise,	they point
  toward the z axis.

  If texturing is turned on (with gluQuadricTexture), then texture
  coordinates are generated so that t ranges linearly from 0.0 at z = 0	to
  1.0 at z = height, and s ranges from 0.0 at the +y axis, to 0.25 at the +x
  axis,	to 0.5 at the -y axis, to 0.75 at the -x axis, and back	to 1.0 at the
  +y axis.

See Also

  gluDisk, gluNewQuadric, gluPartialDisk, gluQuadricTexture, gluSphere

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluDeleteNurbsRenderer.3gl

Name

  gluDeleteNurbsRenderer - destroy a NURBS object

C Specification

  void gluDeleteNurbsRenderer( GLUnurbsObj *nobj )

Parameters

  nobj	Specifies the NURBS object to be destroyed (created with
    gluNewNurbsRenderer).

Description

  gluDeleteNurbsRenderer destroys the NURBS object and frees any memory	used
  by it.  Once gluDeleteNurbsRenderer has been called, nobj cannot be used
  again.

See Also

  gluNewNurbsRenderer

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluDeleteQuadric.3gl

Name

  gluDeleteQuadric - destroy a quadrics	object

C Specification

  void gluDeleteQuadric( GLUquadricObj *state )

Parameters

  state	 Specifies the quadrics	object to be destroyed (created	with
     gluNewQuadric).

Description

  gluDeleteQuadric destroys the	quadrics object	and frees any memory used by
  it.  Once gluDeleteQuadric has been called, state cannot be used again.

See Also

  gluNewQuadric

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluDeleteTess.3gl

gluDeleteTess.3gl (GLU version 1.2 and later)

Name

  gluDeleteTess	- destroy a tessellation object

C Specification

  void gluDeleteTess( GLUtesselator *tess )

Parameters

  tess	Specifies the tessellation object to destroy (created with
    gluNewTess).

Description

  gluDeleteTess	destroys the indicated tessellation object and frees any
  memory that it used.

See Also

  gluBeginPolygon, gluNewTess, gluTessCallback

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluDeleteTess.3gl

gluDeleteTess.3gl (GLU version 1.0 and 1.1)

Name

  gluDeleteTess	- destroy a tessellation object

C Specification

  void gluDeleteTess( GLUtriangulatorObj *tobj )

Parameters

  tobj	Specifies the tessellation object to destroy (created with
    gluNewTess).

Description

  gluDeleteTess	destroys the indicated tessellation object and frees any
  memory that it used.

See Also

  gluBeginPolygon, gluNewTess, gluTessCallback

Introduction | Alphabetic | Specification

Last Edited: Fri, May 26, 1995

AFV


gluDisk.3gl

Name

  gluDisk - draw a disk

C Specification

  void gluDisk(	GLUquadricObj *qobj,
        GLdouble innerRadius,
        GLdouble outerRadius,
        GLint slices,
        GLint loops )

Parameters

  qobj	       Specifies the quadrics object (created with gluNewQuadric).

  innerRadius  Specifies the inner radius of the disk (may be 0).

  outerRadius  Specifies the outer radius of the disk.

  slices       Specifies the number of subdivisions around the z axis.

  loops	       Specifies the number of concentric rings	about the origin into
           which the disk is subdivided.

Description

  gluDisk renders a disk on the	z = 0  plane.  The disk	has a radius of
  outerRadius, and contains a concentric circular hole with a radius of
  innerRadius.	If innerRadius is 0, then no hole is generated.	 The disk is
  subdivided around the	z axis into slices (like pizza slices),	and also
  about	the z axis into	rings (as specified by slices and loops,
  respectively).

  With respect to orientation, the +z side of the disk is considered to	be
  "outside" (see gluQuadricOrientation).  This means that if the orientation
  is set to GLU_OUTSIDE, then any normals generated point along	the +z axis.
  Otherwise, they point	along the -z axis.

  If texturing is turned on (with gluQuadricTexture), texture coordinates are
  generated linearly such that where r=outerRadius, the	value at (r, 0,	0) is
  (1, 0.5), at (0, r, 0) it is (0.5, 1), at (-r, 0, 0) it is (0, 0.5), and at
  (0, -r, 0) it	is (0.5, 0).

See Also

  gluCylinder, gluNewQuadric, gluPartialDisk, gluQuadricOrientation,
  gluQuadricTexture, gluSphere

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluErrorString.3gl

Name

  gluErrorString - produce an error string from	an OpenGL or GLU error code

C Specification

  const	GLubyte* gluErrorString( GLenum	errorCode )

Parameters

  errorCode  Specifies an OpenGL or GLU	error code.

Description

  gluErrorString produces an error string from an OpenGL or GLU	error code.
  The string is	in an ISO Latin	1 format. For example,
  gluErrorString(GL_OUT_OF_MEMORY) returns the string out of memory.

  The standard GLU error codes are GLU_INVALID_ENUM, GLU_INVALID_VALUE,	and
  GLU_OUT_OF_MEMORY.  Certain other GLU	functions can return specialized
  error	codes through callbacks.  Refer	to the glGetError reference page for
  the list of OpenGL error codes.

See Also

  glGetError, gluNurbsCallback,	gluQuadricCallback, gluTessCallback

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluGetNurbsProperty.3gl

Name

  gluGetNurbsProperty -	get a NURBS property

C Specification

  void gluGetNurbsProperty( GLUnurbsObj	*nobj,
                GLenum property,
                GLfloat *value )

Parameters

  nobj	    Specifies the NURBS	object (created	with gluNewNurbsRenderer).

  property  Specifies the property whose value is to be	fetched.  Valid
        values are GLU_CULLING, GLU_SAMPLING_TOLERANCE, GLU_DISPLAY_MODE,
        GLU_AUTO_LOAD_MATRIX, GLU_PARAMETRIC_TOLERANCE,
        GLU_SAMPLING_METHOD, GLU_U_STEP, and GLU_V_STEP.

  value	    Specifies a	pointer	to the location	into which the value of	the
        named  property is written.

Description

  gluGetNurbsProperty is used to retrieve properties stored in a NURBS
  object.  These properties affect the way that	NURBS curves and surfaces are
  rendered.  Please refer to the gluNurbsProperty reference page for
  information about what the properties	are and	what they do.

See Also

  gluNewNurbsRenderer, gluNurbsProperty

Notes

  GLU_PARAMETRIC_TOLERANCE, GLU_SAMPLING_METHOD, GLU_U_STEP, and GLU_V_STEP are
  additions to GLU starting with GLU version 1.1. This version can be distinguished
  during compilation by checking if the pre-processor symbol GLU_VERSION_1_1 is
  defined.

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluGetTessProperty.3gl

gluGetTessProperty.3gl (GLU version 1.2 and later)

Name

  gluGetTessProperty - get a tessellation object property

C Specification

  void gluGetTessProperty( GLUtesselator *tess,
               GLenum which,
               GLdouble *value )

Parameters

  tess	 Specifies the tessellation object (created with gluNewTess).

  which	 Specifies the property	whose value is to be fetched.  Valid values
     are GLU_TESS_WINDING_RULE, GLU_TESS_BOUNDARY_ONLY, and
     GLU_TESS_TOLERANCE.

  value	 Specifies a pointer to	the location into which	the value of the
     named	property is written.

Description

  gluGetTessProperty is	used to	retrieve properties stored in a	tessellation
  object.  These properties affect the way that	tessellation objects are
  interpreted and rendered.  Please refer to the gluTessProperty reference
  page for information about what the properties are and what they do.

See Also

  gluNewTess, gluTessProperty

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluLoadSamplingMatrices.3gl

Name

  gluLoadSamplingMatrices - load NURBS sampling	and culling matrices

C Specification

  void gluLoadSamplingMatrices(	GLUnurbsObj *nobj,
                const GLfloat modelMatrix[16],
                const GLfloat projMatrix[16],
                const GLint viewport[4]); )

Parameters

  nobj	       Specifies the NURBS object (created with	gluNewNurbsRenderer).

  modelMatrix  Specifies a modelview matrix (as	from a glGetFloatv call).

  projMatrix   Specifies a projection matrix (as from a	glGetFloatv call).

  viewport;    Specifies a viewport (as	from a glGetIntegerv call).

Description

  gluLoadSamplingMatrices uses modelMatrix, projMatrix,	and viewport; to
  recompute the	sampling and culling matrices stored in	nobj.  The sampling
  matrix determines how	finely a NURBS curve or	surface	must be	tessellated
  to satisfy the sampling tolerance (as	determined by the
  GLU_SAMPLING_TOLERANCE property).  The  culling matrix is used in deciding
  if a NURBS curve or surface should be	culled before rendering	(when the
  GLU_CULLING property is turned on).

  gluLoadSamplingMatrices is necessary only if the GLU_AUTO_LOAD_MATRIX
  property is turned off (see gluNurbsProperty).  Although it can be
  convenient to	leave the GLU_AUTO_LOAD_MATRIX property	turned on, there can
  be a performance penalty for doing so.  (A round trip	to the OpenGL server
  is needed to fetch the current values	of the modelview matrix, projection
  matrix, and viewport.)

See Also

  gluGetNurbsProperty, gluNewNurbsRenderer, gluNurbsProperty

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluLookAt.3gl

Name

  gluLookAt - define a viewing transformation

C Specification

  void gluLookAt( GLdouble eyex,
          GLdouble eyey,
          GLdouble eyez,
          GLdouble centerx,
          GLdouble centery,
          GLdouble centerz,
          GLdouble upx,
          GLdouble upy,
          GLdouble upz )

Parameters

  eyex,	eyey, eyez
          Specifies the	position of the	eye point.

  centerx, centery, centerz
          Specifies the	position of the	reference point.

  upx, upy, upz	  Specifies the	direction of the up vector.

Description

  gluLookAt creates a viewing matrix derived from an eye point,	a reference
  point	indicating the center of the scene, and	an up vector.  The matrix
  maps the reference point to the negative z axis and the eye point to the
  origin, so that, when	a typical projection matrix is used, the center	of
  the scene maps to the	center of the viewport.	 Similarly, the	direction
  described by the up vector projected onto the	viewing	plane is mapped	to
  the positive y axis so that it points	upward in the viewport.	 The up
  vector must not be parallel to the line of sight from	the eye	to the
  reference point.

  The matrix generated by gluLookAt postmultiplies the current matrix.

See Also

  glFrustum, gluPerspective

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluNewNurbsRenderer.3gl

Name

  gluNewNurbsRenderer -	create a NURBS object

C Specification

  GLUnurbsObj* gluNewNurbsRenderer( void )

Description

  gluNewNurbsRenderer creates and returns a pointer to a new NURBS object.
  This object must be referred to when calling NURBS rendering and control
  functions.  A	return value of	zero means that	there is not enough memory to
  allocate the object.

See Also

  gluBeginCurve, gluBeginSurface, gluBeginTrim,	gluDeleteNurbsRenderer,
  gluNurbsCallback, gluNurbsProperty

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluNewQuadric.3gl

Name

  gluNewQuadric	- create a quadrics object

C Specification

  GLUquadricObj* gluNewQuadric(	void )

Description

  gluNewQuadric	creates	and returns a pointer to a new quadrics	object.	 This
  object must be referred to when calling quadrics rendering and control
  functions.  A	return value of	zero means that	there is not enough memory to
  allocate the object.

See Also

  gluCylinder, gluDeleteQuadric, gluDisk, gluPartialDisk, gluQuadricCallback,
  gluQuadricDrawStyle, gluQuadricNormals, gluQuadricOrientation,
  gluQuadricTexture, gluSphere

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluNewTess.3gl

gluNewTess.3gl (GLU version 1.2 and later)

Name

  gluNewTess - create a	tessellation object

C Specification

  GLUtesselator* gluNewTess( void )

Description

  gluNewTess creates and returns a pointer to a	new tessellation object.
  This object must be referred to when calling tessellation functions.	A
  return value of zero means that there	is not enough memory to	allocate the
  object.

See Also

  gluTessBeginPolygon, gluDeleteTess, gluTessCallback

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluNewTess.3gl

gluNewTess.3gl (GLU versions 1.0 and 1.1)

Name

  gluNewTess - create a	tessellation object

C Specification

  GLUtriangulatorObj* gluNewTess( void )

Description

  gluNewTess creates and returns a pointer to a	new tessellation object.
  This object must be referred to when calling tessellation functions.	A
  return value of zero means that there	is not enough memory to	allocate the
  object.

See Also

  gluBeginPolygon, gluDeleteTess, gluTessCallback

Introduction | Alphabetic | Specification

Last Edited: Fri, May 26, 1995

AFV


gluNextContour.3gl

gluNextContour.3gl (Obsolete in GLU version 1.2 and later)

Name

  gluNextContour - mark	the beginning of another contour

C Specification

  void gluNextContour( GLUtesselator *tess,
               GLenum type )

Parameters

  tess	Specifies the tessellation object (created with	gluNewTess).

  type	Specifies the type of the contour being	defined.  Valid	values are
    GLU_EXTERIOR, GLU_INTERIOR, GLU_UNKNOWN, GLU_CCW, and GLU_CW.

Description

  gluNextContour is used in describing polygons	with multiple contours.
  After	the first contour has been described through a series of
  gluTessVertex	calls, a gluNextContour	call indicates that the	previous
  contour is complete and that the next	contour	is about to begin.  Another
  series of gluTessVertex calls	is then	used to	describe the new contour.
  This process can be repeated until all contours have been described.

  type defines what type of contour follows.  The legal	contour	types are as
  follows:

  GLU_EXTERIOR	 An exterior contour defines an	exterior boundary of the
         polygon.

  GLU_INTERIOR	 An interior contour defines an	interior boundary of the
         polygon (such as a hole).

  GLU_UNKNOWN	 An unknown contour is analyzed	by the library to determine
         if it is interior or exterior.

  GLU_CCW, GLU_CW
         The first GLU_CCW or GLU_CW contour defined is	considered to
         be exterior.  All other contours are considered to be
         exterior if they are oriented in the same direction
         (clockwise or counterclockwise) as the	first contour, and
         interior if they are not.

         If one	contour	is of type GLU_CCW or GLU_CW, then all
         contours must be of the same type (if they are	not, then all
         GLU_CCW and GLU_CW contours will be changed to	GLU_UNKNOWN).

         Note that there is no real difference between the GLU_CCW
         and GLU_CW contour types.

  gluNextContour can be	called before the first	contour	is described to
  define the type of the first contour.	 If gluNextContour is not called
  before the first contour, then the first contour is marked GLU_EXTERIOR.

Notes

  As of GLU version 1.2, this command is obsolete and is provided for backwards
  compatibility only. GLU version 1.2 (and later) can be distinguished during
  compilation by checking if the pre-processor symbol GLU_VERSION_1_2 is defined.

  Calls	to gluNextContour are mapped to	gluTessEndContour followed by
  gluTessBeginContour.

Example

  A quadrilateral with a triangular hole in it can be described	as follows:

  gluBeginPolygon(tobj);
     gluTessVertex(tobj, v1, v1);
     gluTessVertex(tobj, v2, v2);
     gluTessVertex(tobj, v3, v3);
     gluTessVertex(tobj, v4, v4);
  gluNextContour(tobj, GLU_INTERIOR);
     gluTessVertex(tobj, v5, v5);
     gluTessVertex(tobj, v6, v6);
     gluTessVertex(tobj, v7, v7);
  gluEndPolygon(tobj);

See Also

   gluBeginPolygon, gluNewTess,	gluTessCallback, gluTessVertex,
  gluTessBeginContour

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluNextContour.3gl

gluNextContour.3gl (GLU versions 1.0 and 1.1)

Name

  gluNextContour - mark	the beginning of another contour

C Specification

  void gluNextContour( GLUtriangulatorObj *tobj,
               GLenum type )

Parameters

  tobj	Specifies the tessellation object (created with	gluNewTess).

  type	Specifies the type of the contour being	defined.  Valid	values are
    GLU_EXTERIOR, GLU_INTERIOR, GLU_UNKNOWN, GLU_CCW, and GLU_CW.

Description

  gluNextContour is used in describing polygons	with multiple contours.
  After	the first contour has been described through a series of
  gluTessVertex	calls, a gluNextContour	call indicates that the	previous
  contour is complete and that the next	contour	is about to begin.  Another
  series of gluTessVertex calls	is then	used to	describe the new contour.
  This process can be repeated until all contours have been described.

  type defines what type of contour follows.  The legal	contour	types are as
  follows:

  GLU_EXTERIOR	 An exterior contour defines an	exterior boundary of the
         polygon.

  GLU_INTERIOR	 An interior contour defines an	interior boundary of the
         polygon (such as a hole).

  GLU_UNKNOWN	 An unknown contour is analyzed	by the library to determine
         if it is interior or exterior.

  GLU_CCW, GLU_CW
         The first GLU_CCW or GLU_CW contour defined is	considered to
         be exterior.  All other contours are considered to be
         exterior if they are oriented in the same direction
         (clockwise or counterclockwise) as the	first contour, and
         interior if they are not.

         If one	contour	is of type GLU_CCW or GLU_CW, then all
         contours must be of the same type (if they are	not, then all
         GLU_CCW and GLU_CW contours will be changed to	GLU_UNKNOWN).

         Note that there is no real difference between the GLU_CCW
         and GLU_CW contour types.

  gluNextContour can be	called before the first	contour	is described to
  define the type of the first contour.	 If gluNextContour is not called
  before the first contour, then the first contour is marked GLU_EXTERIOR.

Example

  A quadrilateral with a triangular hole in it can be described	as follows:

  gluBeginPolygon(tobj);
     gluTessVertex(tobj, v1, v1);
     gluTessVertex(tobj, v2, v2);
     gluTessVertex(tobj, v3, v3);
     gluTessVertex(tobj, v4, v4);
  gluNextContour(tobj, GLU_INTERIOR);
     gluTessVertex(tobj, v5, v5);
     gluTessVertex(tobj, v6, v6);
     gluTessVertex(tobj, v7, v7);
  gluEndPolygon(tobj);

See Also

   gluBeginPolygon, gluNewTess,	gluTessCallback, gluTessVertex

Introduction | Alphabetic | Specification

Last Edited: Fri, May 26, 1995

AFV


gluNurbsCallback.3gl

Name

  gluNurbsCallback - define a callback for a NURBS object

C Specification

  void gluNurbsCallback( GLUnurbsObj *nobj,
             GLenum	which,
             void (*fn)( )

Parameters

  nobj	 Specifies the NURBS object (created with gluNewNurbsRenderer).

  which	 Specifies the callback	being defined.	The only valid value is
     GLU_ERROR.

  fn	 Specifies the function	that the callback calls.

Description

  gluNurbsCallback is used to define a callback	to be used by a	NURBS object.
  If the specified callback is already defined,	then it	is replaced.  If fn
  is NULL, then	any existing callback is erased.

  The one legal	callback is GLU_ERROR:

  GLU_ERROR	 The error function is called when an error is encountered.
         Its single argument is	of type	GLenum,	and it indicates the
         specific error	that occurred.	There are 37 errors unique to
         NURBS named GLU_NURBS_ERROR1 through GLU_NURBS_ERROR37.
         Character strings describing these errors can be retrieved
         with gluErrorString.

See Also

  gluErrorString, gluNewNurbsRenderer

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluNurbsCurve.3gl

Name

  gluNurbsCurve	- define the shape of a	NURBS curve

C Specification

  void gluNurbsCurve( GLUnurbsObj *nobj,
              GLint nknots,
              GLfloat *knot,
              GLint stride,
              GLfloat *ctlarray,
              GLint order,
              GLenum type )

Parameters

  nobj	    Specifies the NURBS	object (created	with gluNewNurbsRenderer).

  nknots    Specifies the number of knots in knot.  nknots equals the number
        of control points plus the order.

  knot	    Specifies an array of nknots nondecreasing knot values.

  stride    Specifies the offset (as a number of single-precision floating-
        point values) between successive curve control points.

  ctlarray  Specifies a	pointer	to an array of control points.	The
        coordinates	must agree with	type, specified	below.

  order	    Specifies the order	of the NURBS curve.  order equals degree + 1,
        hence a cubic curve	has an order of	4.

  type	    Specifies the type of the curve.  If this curve is defined within
        a gluBeginCurve/gluEndCurve	pair, then the type can	be any of the
        valid one-dimensional evaluator types (such	as GL_MAP1_VERTEX_3
        or GL_MAP1_COLOR_4).  Between a gluBeginTrim/gluEndTrim pair, the
        only valid types are GLU_MAP1_TRIM_2 and GLU_MAP1_TRIM_3.

Description

  Use gluNurbsCurve to describe	a NURBS	curve.

  When gluNurbsCurve appears between a gluBeginCurve/gluEndCurve pair, it is
  used to describe a curve to be rendered.  Positional,	texture, and color
  coordinates are associated by	presenting each	as a separate gluNurbsCurve
  between a gluBeginCurve/gluEndCurve pair.  No	more than one call to
  gluNurbsCurve	for each of color, position, and texture data can be made
  within a single gluBeginCurve/gluEndCurve pair.  Exactly one call must be
  made to describe the position	of the curve (a	type of	GL_MAP1_VERTEX_3 or
  GL_MAP1_VERTEX_4).

  When gluNurbsCurve appears between a gluBeginTrim/gluEndTrim pair, it	is
  used to describe a trimming curve on a NURBS surface.	 If type is
  GLU_MAP1_TRIM_2, then	it describes a curve in	two-dimensional	(u and v)
  parameter space.  If it is GLU_MAP1_TRIM_3, then it describes	a curve	in
  two-dimensional homogeneous (u, v, and w) parameter space.  See the
  gluBeginTrim reference page for more discussion about	trimming curves.

Example

  The following	commands render	a textured NURBS curve with normals:

  gluBeginCurve(nobj);
     gluNurbsCurve(nobj, ..., GL_MAP1_TEXTURE_COORD_2);
     gluNurbsCurve(nobj, ..., GL_MAP1_NORMAL);
     gluNurbsCurve(nobj, ..., GL_MAP1_VERTEX_4); gluEndCurve(nobj);

Notes

  To define trim curves	which stitch well use gluPwlCurve.

See Also

  gluBeginCurve, gluBeginTrim, gluNewNurbsRenderer, gluPwlCurve

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluNurbsProperty.3gl

Name

  gluNurbsProperty - set a NURBS property

C Specification

  void gluNurbsProperty( GLUnurbsObj *nobj,
             GLenum	property,
             GLfloat value )

Parameters

  nobj	    Specifies the NURBS	object (created	with gluNewNurbsRenderer).

  property  Specifies the property to be set.  Valid values are
        GLU_SAMPLING_TOLERANCE, GLU_DISPLAY_MODE, GLU_CULLING,
        GLU_AUTO_LOAD_MATRIX, GLU_PARAMETRIC_TOLERANCE,
        GLU_SAMPLING_METHOD, GLU_U_STEP, or	GLU_V_STEP.

  value	    Specifies the value	of the indicated property.  It may be a
        numeric value, or one of GLU_PATH_LENGTH, GLU_PARAMETRIC_ERROR,
        or GLU_DOMAIN_DISTANCE.

Description

  gluNurbsProperty is used to control properties stored	in a NURBS object.
  These	properties affect the way that a NURBS curve is	rendered.  The legal
  values for property are as follows:

  GLU_SAMPLING_METHOD	   specifies how a NURBS surface should	be
               tessellated.	value may be set to one	of
               GLU_PATH_LENGTH, GLU_PARAMETRIC_ERROR, or
               GLU_DOMAIN_DISTANCE.	When set to GLU_PATH_LENGTH,
               the surface is rendered so that the maximum
               length, in pixels, of the edges of the
               tessellation	polygons is no greater than what is
               specified by	GLU_SAMPLING_TOLERANCE.

               GLU_PARAMETRIC_ERROR	specifies that the surface is
               rendered in such a way that the value specified by
               GLU_PARAMETRIC_TOLERANCE describes the maximum
               distance, in	pixels,	between	the tessellation
               polygons and	the surfaces they approximate.

               GLU_DOMAIN_DISTANCE allows users to specify,	in
               parametric coordinates, how many sample points per
               unit	length are taken in u, v direction. The
               default value of GLU_SAMPLING_METHOD	is
               GLU_PATH_LENGTH.

  GLU_SAMPLING_TOLERANCE   specifies the maximum length, in pixels to use
               when	the sampling method is set to
               GLU_PATH_LENGTH. The	NURBS code is conservative
               when	rendering a curve or surface, so the actual
               length can be somewhat shorter. The default value
               is 50.0 pixels.

  GLU_PARAMETRIC_TOLERANCE specifies the maximum distance, in pixels, to use
               when	the sampling method is set to
               GLU_PARAMETRIC_ERROR.  The default value for
               GLU_PARAMETRIC_TOLERANCE is 0.5.

  GLU_U_STEP		   specifies the number	of sample points per unit
               length taken	along the u axis in parametric
               coordinates.	It is needed when GLU_SAMPLING_METHOD
               is set to GLU_DOMAIN_DISTANCE. The default value
               is 100.

  GLU_V_STEP		   specifies the number	of sample points per unit
               length taken	along the v axis in parametric
               coordinate. It is needed when GLU_SAMPLING_METHOD
               is set to GLU_DOMAIN_DISTANCE.  The default value
               is 100.

  GLU_DISPLAY_MODE	   value defines how a NURBS surface should be
               rendered.  value can	be set to GLU_FILL,
               GLU_OUTLINE_POLYGON,	or GLU_OUTLINE_PATCH.  When
               set to GLU_FILL, the	surface	is rendered as a set
               of polygons.	 GLU_OUTLINE_POLYGON instructs the
               NURBS library to draw only the outlines of the
               polygons created by tessellation.
               GLU_OUTLINE_PATCH causes just the outlines of
               patches and trim curves defined by the user to be
               drawn.  The default value is	GLU_FILL.

  GLU_CULLING		   value is a Boolean value that, when set to
               GL_TRUE, indicates that a NURBS curve should	be
               discarded prior to tessellation if its control
               points lie outside the current viewport.  The
               default is GL_FALSE (because	a NURBS	curve cannot
               fall	entirely within	the convex hull	of its
               control points).

  GLU_AUTO_LOAD_MATRIX	   value is a Boolean value.  When set to GL_TRUE,
               the NURBS code downloads the	projection matrix,
               the modelview matrix, and the viewport from the
               OpenGL server to compute sampling and culling
               matrices for	each NURBS curve that is rendered.
               Sampling and	culling	matrices are required to
               determine the tesselation of	a NURBS	surface	into
               line	segments or polygons and to cull a NURBS
               surface if it lies outside of the viewport.	If
               this	mode is	set to GL_FALSE, then the user needs
               to provide a	projection matrix, a modelview
               matrix, and a viewport for the NURBS	renderer to
               use to construct sampling and culling matrices.
               This	can be done with the gluLoadSamplingMatrices
               function.  The default for this mode	is GL_TRUE.
               Changing this mode from GL_TRUE to GL_FALSE does
               not affect the sampling and culling matrices	until
               gluLoadSamplingMatrices is called.

Notes

  A property of	GLU_PARAMETRIC_TOLERANCE, GLU_SAMPLING_METHOD, GLU_U_STEP, or
  GLU_V_STEP, or a value of GLU_PATH_LENGTH, GLU_PARAMETRIC_ERROR,
  GLU_DOMAIN_DISTANCE will only	be supported in	GLU version number 1.1.	They
  are not valid	parameters in GLU 1.0.

  gluGetString can be used to determine	the GLU	version.

See Also

  gluGetNurbsProperty, gluLoadSamplingMatrices,	gluNewNurbsRenderer,
  gluGetString.

Introduction | Alphabetic | Specification

Last Edited: Thu Mar 16, 1995

AFV


gluNurbsSurface.3gl

Name

  gluNurbsSurface - define the shape of	a NURBS	surface

C Specification

  void gluNurbsSurface(	GLUnurbsObj *nobj,
            GLint uknot_count,
            GLfloat	*uknot,
            GLint vknot_count,
            GLfloat	*vknot,
            GLint u_stride,
            GLint v_stride,
            GLfloat	*ctlarray,
            GLint uorder,
            GLint vorder,
            GLenum type )

Parameters

  nobj	       Specifies the NURBS object (created with	gluNewNurbsRenderer).

  uknot_count  Specifies the number of knots in	the parametric u direction.

  uknot	       Specifies an array of uknot_count nondecreasing knot values in
           the parametric u	direction.

  vknot_count  Specifies the number of knots in	the parametric v direction.

  vknot	       Specifies an array of vknot_count nondecreasing knot values in
           the parametric v	direction.

  u_stride     Specifies the offset (as	a number of  single-precision
           floating	point values) between successive control points	in
           the parametric u	direction in ctlarray.

  v_stride     Specifies the offset (in	single-precision floating-point
           values) between successive control points in the	parametric v
           direction in ctlarray.

  ctlarray     Specifies an array containing control points for	the NURBS
           surface.	 The offsets between successive	control	points in the
           parametric u and	v directions are given by u_stride and
           v_stride.

  uorder       Specifies the order of the NURBS	surface	in the parametric u
           direction.  The order is	one more than the degree, hence	a
           surface that is cubic in	u has a	u order	of 4.

  vorder       Specifies the order of the NURBS	surface	in the parametric v
           direction.  The order is	one more than the degree, hence	a
           surface that is cubic in	v has a	v order	of 4.

  type	       Specifies type of the surface. type can be any of the valid
           two-dimensional evaluator types (such as	GL_MAP2_VERTEX_3 or
           GL_MAP2_COLOR_4).

Description

  Use gluNurbsSurface within a NURBS (Non-Uniform Rational B-Spline) surface
  definition to	describe the shape of a	NURBS surface (before any trimming).
  To mark the beginning	of a NURBS surface definition, use the
  gluBeginSurface command.  To mark the	end of a NURBS surface definition,
  use the gluEndSurface	command.  Call gluNurbsSurface within a	NURBS surface
  definition only.

  Positional, texture, and color coordinates are associated with a surface by
  presenting each as a separate	gluNurbsSurface	between	a
  gluBeginSurface/gluEndSurface	pair.  No more than one	call to
  gluNurbsSurface for each of color, position, and texture data	can be made
  within a single gluBeginSurface/gluEndSurface	pair.  Exactly one call	must
  be made to describe the position of the surface (a type of GL_MAP2_VERTEX_3
  or GL_MAP2_VERTEX_4).

  A NURBS surface can be trimmed by using the commands gluNurbsCurve and
  gluPwlCurve between calls to gluBeginTrim and	gluEndTrim.

  Note that a gluNurbsSurface with uknot_count knots in	the u direction	and
  vknot_count knots in the v direction with orders uorder and vorder must
  have (uknot_count - uorder) x	(vknot_count - vorder) control points.

Example

  The following	commands render	a textured NURBS surface with normals; the
  texture coordinates and normals are also NURBS surfaces:

  gluBeginSurface(nobj);
     gluNurbsSurface(nobj, ...,	GL_MAP2_TEXTURE_COORD_2);
     gluNurbsSurface(nobj, ...,	GL_MAP2_NORMAL);
     gluNurbsSurface(nobj, ...,	GL_MAP2_VERTEX_4); gluEndSurface(nobj);

See Also

  gluBeginSurface, gluBeginTrim, gluNewNurbsRenderer, gluNurbsCurve,
  gluPwlCurve

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluOrtho2D.3gl

Name

  gluOrtho2D - define a	2-D orthographic projection matrix

C Specification

  void gluOrtho2D( GLdouble left,
           GLdouble right,
           GLdouble bottom,
           GLdouble top	)

Parameters

  left,	right Specify the coordinates for the left and right vertical
          clipping planes.

  bottom, top Specify the coordinates for the bottom and top horizontal
          clipping planes.

Description

  gluOrtho2D sets up a two-dimensional orthographic viewing region. This is
  equivalent to	calling	glOrtho	with near=-1 and far=1.

See Also

  glOrtho, gluPerspective

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluPartialDisk.3gl

Name

  gluPartialDisk - draw	an arc of a disk

C Specification

  void gluPartialDisk( GLUquadricObj *qobj,
               GLdouble	innerRadius,
               GLdouble	outerRadius,
               GLint slices,
               GLint loops,
               GLdouble	startAngle,
               GLdouble	sweepAngle )

Parameters

  qobj	       Specifies a quadrics object (created with gluNewQuadric).

  innerRadius  Specifies the inner radius of the partial disk (can be zero).

  outerRadius  Specifies the outer radius of the partial disk.

  slices       Specfies	the number of subdivisions around the z	axis.

  loops	       Specifies the number of concentric rings	about the origin into
           which the partial disk is subdivided.

  startAngle   Specifies the starting angle, in	degrees, of the	disk portion.

  sweepAngle   Specifies the sweep angle, in degrees, of the disk portion.

Description

  gluPartialDisk renders a partial disk	on the z=0 plane.  A partial disk is
  similar to a full disk, except that only the subset of the disk from
  startAngle through startAngle	+ sweepAngle is	included (where	0 degrees is
  along	the +y axis, 90	degrees	along the +x axis, 180 along the -y axis, and
  270 along the	-x axis).

  The partial disk has a radius	of outerRadius,	and contains a concentric
  circular hole	with a radius of innerRadius.  If innerRadius is zero, then
  no hole is generated.	 The partial disk is subdivided	around the z axis
  into slices (like pizza slices), and also about the z	axis into rings	(as
  specified by slices and loops, respectively).

  With respect to orientation, the +z side of the partial disk is considered
  to be	outside	(see gluQuadricOrientation). This means	that if	the
  orientation is set to	GLU_OUTSIDE, then any normals generated	point along
  the +z axis.	Otherwise, they	point along the	-z axis.

  If texturing is turned on (with gluQuadricTexture), texture coordinates are
  generated linearly such that where r=outerRadius, the	value at (r, 0,	0) is
  (1, 0.5), at (0, r, 0) it is (0.5, 1), at (-r, 0, 0) it is (0, 0.5), and at
  (0, -r, 0) it	is (0.5, 0).

See Also

  gluCylinder, gluDisk,	gluNewQuadric, gluQuadricOrientation,
  gluQuadricTexture, gluSphere

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluPerspective.3gl

Name

  gluPerspective - set up a perspective	projection matrix

C Specification

  void gluPerspective( GLdouble	fovy,
               GLdouble	aspect,
               GLdouble	zNear,
               GLdouble	zFar )

Parameters

  fovy	  Specifies the	field of view angle, in	degrees, in the	y direction.

  aspect  Specifies the	aspect ratio that determines the field of view in the
      x direction.	The aspect ratio is the	ratio of x (width) to y
      (height).

  zNear	  Specifies the	distance from the viewer to the	near clipping plane
      (always positive).

  zFar	  Specifies the	distance from the viewer to the	far clipping plane
      (always positive).

Description

  gluPerspective specifies a viewing frustum into the world coordinate
  system.  In general, the aspect ratio	in gluPerspective should match the
  aspect ratio of the associated viewport.  For	example, aspect=2.0 means the
  viewer's angle of view is twice as wide in x as it is	in y. If the viewport
  is twice as wide as it is tall, it displays the image	without	distortion.

  The matrix generated by gluPerspective is multipled by the current matrix,
  just as if glMultMatrix were called with the generated matrix.  To load the
  perspective matrix onto the current matrix stack instead, precede the	call
  to gluPerspective with a call	to glLoadIdentity.

  Given	f defined as follows:
                                            fovy
                  f	= cotangent(----)
                          2
  The generated	matrix is

          |    f				   |
                  |  ------  0      0            0         |
          |  aspect   	    		 	   |
          |					   |
          |    0     f	    0		 0	   |
          |					   |
          |		zFar+zNear  2*zFar*zNear   |
                  |    0     0  ----------  ------------   |
          |         	zNear-zFar   zNear-zFar	   |
          |					   |
          |    0     0	    -1		 0         |

Notes

  Depth	buffer precision is affected by	the values specified for zNear and
  zFar.	 The greater the ratio of zFar to zNear	is, the	less effective the
  depth	buffer will be at distinguishing between surfaces that are near	each
  other.  If
                                       zFar
                   r = -----
                       zNear

  roughly log r	bits of	depth buffer precision are lost.  Because r
             2
  approaches infinity as zNear approaches zero,	zNear must never be set	to
  zero.

See Also

  glFrustum, glLoadIdentity, glMultMatrix, gluOrtho2D

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluPickMatrix.3gl

Name

  gluPickMatrix	- define a picking region

C Specification

  void gluPickMatrix( GLdouble x,
              GLdouble y,
              GLdouble width,
              GLdouble height,
              GLint viewport[4]	)

Parameters

  x, y Specify the center of a picking region in window	coordinates.

  width, height
       Specify the width and height, respectively, of the picking region in
       window coordinates.

  viewport
       Specifies the current viewport (as from a glGetIntegerv call).

Description

  gluPickMatrix	creates	a projection matrix that can be	used to	restrict
  drawing to a small region of the viewport.  This is typically	useful to
  determine what objects are being drawn near the cursor.  Use gluPickMatrix
  to restrict drawing to a small region	around the cursor.  Then, enter
  selection mode (with glRenderMode and	rerender the scene.  All primitives
  that would have been drawn near the cursor are identified and	stored in the
  selection buffer.

  The matrix created by	gluPickMatrix is multiplied by the current matrix
  just as if glMultMatrix is called with the generated matrix.	To
  effectively use the generated	pick matrix for	picking, first call
  glLoadIdentity to load an identity matrix onto the perspective matrix
  stack.  Then call gluPickMatrix, and finally,	call a command (such as
  gluPerspective) to multiply the perspective matrix by	the pick matrix.

  When using gluPickMatrix to pick NURBS, be careful to	turn off the NURBS
  property GLU_AUTO_LOAD_MATRIX.  If GLU_AUTO_LOAD_MATRIX is not turned	off,
  then any NURBS surface rendered is subdivided	differently with the pick
  matrix than the way it was subdivided	without	the pick matrix.

Example

  When rendering a scene as follows:

  glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(...);
  glMatrixMode(GL_MODELVIEW); /* Draw the scene	*/

  a portion of the viewport can	be selected as a pick region like this:

  glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPickMatrix(x, y, width,
  height, viewport); gluPerspective(...); glMatrixMode(GL_MODELVIEW); /* Draw
  the scene */

See Also

  glGet, glLoadIndentity, glMultMatrix,	glRenderMode, gluPerspective

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluProject.3gl

Name

  gluProject - map object coordinates to window	coordinates

C Specification

  int gluProject( GLdouble objx,
          GLdouble objy,
          GLdouble objz,
          const	GLdouble modelMatrix[16],
          const	GLdouble projMatrix[16],
          const	GLint viewport[4],
          GLdouble *winx,
          GLdouble *winy,
          GLdouble *winz )

Parameters

  objx,	objy, objz
          Specify the object coordinates.

  modelMatrix	  Specifies the	current	modelview matrix (as from a
          glGetDoublev call).

  projMatrix	  Specifies the	current	projection matrix (as from a
          glGetDoublev call).

  viewport	  Specifies the	current	viewport (as from a glGetIntegerv
          call).

  winx,	winy, winz
          Return the computed window coordinates.

Description

  gluProject transforms	the specified object coordinates into window
  coordinates using modelMatrix, projMatrix, and viewport.  The	result is
  stored in winx, winy,	and winz.  A return value of GL_TRUE indicates
  success, and GL_FALSE	indicates failure.

See Also

  glGet, gluUnProject

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluPwlCurve.3gl

Name

  gluPwlCurve -	describe a piecewise linear NURBS trimming curve

C Specification

  void gluPwlCurve( GLUnurbsObj	*nobj,
            GLint count,
            GLfloat *array,
            GLint stride,
            GLenum type	)

Parameters

  nobj	  Specifies the	NURBS object (created with gluNewNurbsRenderer).

  count	  Specifies the	number of points on the	curve.

  array	  Specifies an array containing	the curve points.

  stride  Specifies the	offset (a number of single-precision floating-point
      values) between points on the	curve.

  type	  Specifies the	type of	curve.	Must be	either GLU_MAP1_TRIM_2 or
      GLU_MAP1_TRIM_3.

Description

  gluPwlCurve describes	a piecewise linear trimming curve for a	NURBS
  surface.  A piecewise	linear curve consists of a list	of coordinates of
  points in the	parameter space	for the	NURBS surface to be trimmed.  These
  points are connected with line segments to form a curve.  If the curve is
  an approximation to a	real curve, the	points should be close enough that
  the resulting	path appears curved at the resolution used in the
  application.

  If type is GLU_MAP1_TRIM_2, then it describes	a curve	in two-dimensional (u
  and v) parameter space.  If it is GLU_MAP1_TRIM_3, then it describes a
  curve	in two-dimensional homogeneous (u, v, and w) parameter space.  Please
  refer	to the	gluBeginTrim reference page for	more information about
  trimming curves.

Notes

  To describe a	trim curve which closely follows the contours of a Nurbs
  surface use gluNurbsCurve.

See Also

  gluBeginCurve, gluBeginTrim, gluNewNurbsRenderer, gluNurbsCurve

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluQuadricCallback.3gl

Name

  gluQuadricCallback - define a	callback for a quadrics	object

C Specification

  void gluQuadricCallback( GLUquadricObj *qobj,
               GLenum which,
               void	(*fn)( )

Parameters

  qobj	 Specifies the quadrics	object (created	with gluNewQuadric).

  which	 Specifies the callback	being defined.	The only valid value is
     GLU_ERROR.

  fn	 Specifies the function	to be called.

Description

  gluQuadricCallback is	used to	define a new callback to be used by a
  quadrics object.  If the specified callback is already defined, then it is
  replaced.  If	fn is NULL, then any existing callback is erased.

  The one legal	callback is GLU_ERROR:

  GLU_ERROR	 The function is called	when an	error is encountered.  Its
         single	argument is of type GLenum, and	it indicates the
         specific error	that occurred.	Character strings describing
         these errors can be retrieved with the	gluErrorString call.

See Also

  gluErrorString, gluNewQuadric

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluQuadricDrawStyle.3gl

Name

  gluQuadricDrawStyle -	specify	the draw style desired for quadrics

C Specification

  void gluQuadricDrawStyle( GLUquadricObj *qobj,
                GLenum drawStyle )

Parameters

  qobj	     Specifies the quadrics object (created with gluNewQuadric).

  drawStyle  Specifies the desired draw	style.	Valid values are GLU_FILL,
         GLU_LINE, GLU_SILHOUETTE, and GLU_POINT.

Description

  gluQuadricDrawStyle specifies	the draw style for quadrics rendered with
  qobj.	 The legal values are as follows:

  GLU_FILL	 Quadrics are rendered with polygon primitives.	 The polygons
         are drawn in a	counterclockwise fashion with respect to
         their normals (as defined with	gluQuadricOrientation).

  GLU_LINE	 Quadrics are rendered as a set	of lines.

  GLU_SILHOUETTE Quadrics are rendered as a set	of lines, except that edges
         separating coplanar faces will	not be drawn.

  GLU_POINT	 Quadrics are rendered as a set	of points.

See Also

  gluNewQuadric, gluQuadricNormals, gluQuadricOrientation,  gluQuadricTexture

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluQuadricNormals.3gl

Name

  gluQuadricNormals - specify what kind	of normals are desired for quadrics

C Specification

  void gluQuadricNormals( GLUquadricObj	*qobj,
              GLenum normals )

Parameters

  qobj	   Specifes the	quadrics object	(created with gluNewQuadric).

  normals  Specifies the desired type of normals.  Valid values	are GLU_NONE,
       GLU_FLAT, and GLU_SMOOTH.

Description

  gluQuadricNormals specifies what kind	of normals are desired for quadrics
  rendered with	qobj.  The legal values	are as follows:

  GLU_NONE     No normals are generated.

  GLU_FLAT     One normal is generated for every facet of a quadric.

  GLU_SMOOTH   One normal is generated for every vertex	of a quadric.  This
           is the default.

See Also

  gluNewQuadric, gluQuadricDrawStyle, gluQuadricOrientation,
  gluQuadricTexture

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluQuadricOrientation.3gl

Name

  gluQuadricOrientation	- specify inside/outside orientation for quadrics

C Specification

  void gluQuadricOrientation( GLUquadricObj *qobj,
                  GLenum orientation )

Parameters

  qobj	       Specifies the quadrics object (created with gluNewQuadric).

  orientation  Specifies the desired orientation.  Valid values	are
           GLU_OUTSIDE and GLU_INSIDE.

Description

  gluQuadricOrientation	specifies what kind of orientation is desired for
  quadrics rendered with qobj.	The orientation	values are as follows:

  GLU_OUTSIDE	 Quadrics are drawn with normals pointing outward.

  GLU_INSIDE	 Normals point inward.	The default is GLU_OUTSIDE.

  Note that the	interpretation of outward and inward depends on	the quadric
  being	drawn.

See Also

  gluNewQuadric, gluQuadricDrawStyle, gluQuadricNormals, gluQuadricTexture

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluQuadricTexture.3gl

Name

  gluQuadricTexture - specify if texturing is desired for quadrics

C Specification

  void gluQuadricTexture( GLUquadricObj	*qobj,
              GLboolean textureCoords )

Parameters

  qobj		 Specifies the quadrics	object (created	with gluNewQuadric).

  textureCoords	 Specifies a flag indicating if	texture	coordinates should be
         generated.

Description

  gluQuadricTexture specifies if texture coordinates should be generated for
  quadrics rendered with qobj.	If the value of	textureCoords is GL_TRUE,
  then texture coordinates are generated, and if textureCoords is GL_FALSE,
  they are not.	 The default is	GL_FALSE.

  The manner in	which texture coordinates are generated	depends	upon the
  specific quadric rendered.

See Also

  gluNewQuadric, gluQuadricDrawStyle, gluQuadricNormals,
  gluQuadricOrientation

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluScaleImage.3gl

Name

  gluScaleImage	- scale	an image to an arbitrary size

C Specification

  int gluScaleImage( GLenum format,
             GLint widthin,
             GLint heightin,
             GLenum typein,
             const void	*datain,
             GLint widthout,
             GLint heightout,
             GLenum typeout,
             void *dataout )

Parameters

  format     Specifies the format of the pixel data.  The following symbolic
         values are	valid: GL_COLOR_INDEX, GL_STENCIL_INDEX,
         GL_DEPTH_COMPONENT, GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_RGB,
         GL_RGBA, GL_LUMINANCE, and	GL_LUMINANCE_ALPHA.

  widthin, heightin
         Specify the width and height, respectively, of the	source image
         that is scaled.

  typein     Specifies the data	type for datain. Must be one of
         GL_UNSIGNED_BYTE, GL_BYTE,	GL_BITMAP, GL_UNSIGNED_SHORT,
         GL_SHORT, GL_UNSIGNED_INT,	GL_INT,	or GL_FLOAT.

  datain     Specifies a pointer to the	source image.

  widthout, heightout
         Specify the width and height, respectively, of the	destination
         image.

  typeout    Specifies the data	type for dataout. Must be one of
         GL_UNSIGNED_BYTE, GL_BYTE,	GL_BITMAP, GL_UNSIGNED_SHORT,
         GL_SHORT, GL_UNSIGNED_INT,	GL_INT,	or GL_FLOAT.

  dataout    Specifies a pointer to the	destination image.

Description

  gluScaleImage	scales a pixel image using the appropriate pixel store modes
  to unpack data from the source image and pack	data into the destination
  image.

  When shrinking an image, gluScaleImage uses a	box filter to sample the
  source image and create pixels for the destination image.  When magnifying
  an image, the	pixels from the	source image are linearly interpolated to
  create the destination image.

  A return value of zero indicates success, otherwise a	GLU error code is
  returned indicating what the problem was (see	gluErrorString).

  Please refer to the glReadPixels reference page for a	description of the
  acceptable values for	the format, typein, and	typeout	parameters.

See Also

  glDrawPixels,	glReadPixels, gluBuild1DMipmaps, gluBuild2DMipmaps,
  gluErrorString

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluSphere.3gl

Name

  gluSphere - draw a sphere

C Specification

  void gluSphere( GLUquadricObj	*qobj,
          GLdouble radius,
          GLint	slices,
          GLint	stacks )

Parameters

  qobj	  Specifies the	quadrics object	(created with gluNewQuadric).

  radius  Specifies the	radius of the sphere.

  slices  Specifies the	number of subdivisions around the z axis (similar to
      lines	of longitude).

  stacks  Specifies the	number of subdivisions along the z axis	(similar to
      lines	of latitude).

Description

  gluSphere draws a sphere of the given	radius centered	around the origin.
  The sphere is	subdivided around the z	axis into slices and along the z axis
  into stacks (similar to lines	of longitude and latitude).

  If the orientation is	set to GLU_OUTSIDE (with gluQuadricOrientation), then
  any normals generated	point away from	the center of the sphere. Otherwise,
  they point toward the	center of the sphere.

  If texturing is turned on (with gluQuadricTexture), then texture
  coordinates are generated so that t ranges from 0.0 at z=-radius to 1.0 at
  z=radius (t increases	linearly along longitudinal lines), and	s ranges from
  0.0 at the +y	axis, to 0.25 at the +x	axis, to 0.5 at	the -y axis, to	0.75
  at the -x axis, and back to 1.0 at the +y axis.

See Also

  gluCylinder, gluDisk,	gluNewQuadric, gluPartialDisk, gluQuadricOrientation
  gluQuadricTexture

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluTessBeginContour.3gl

gluTessBeginContour.3gl (GLU version 1.2 and later)

Name

  gluTessBeginContour, gluTessEndContour - delimit a contour description

C Specification

  void gluTessBeginContour( GLUtesselator *tess	)

  void gluTessEndContour( GLUtesselator	*tess )

Parameters

  tess	Specifies the tessellation object (created with	gluNewTess).

Description

  gluTessBeginContour and gluTessEndContour delimit the	definition of a
  polygon contour. Within each gluTessBeginContour/gluTessEndContour pair,
  there	can be zero or more calls to gluTessVertex. The	vertices specify a
  closed contour (the last vertex of each contour is automatically linked to
  the first).  See the gluTessVertex reference page for	more details.
  gluTessBeginContour can only be called between gluTessBeginPolygon and
  gluTessEndPolygon.

See Also

  gluNewTess, gluTessBeginPolygon, gluTessVertex, gluTessCallback,
  gluTessProperty, gluTessNormal, gluTessEndPolygon

Introduction | Alphabetic | Specification

Last Edited: Fri, May 26, 1995

AFV


gluTessBeginPolygon.3gl

gluTessBeginPolygon.3gl (GLU version 1.2 and later)

Name

  gluTessBeginPolygon -	delimit	a polygon description

C Specification

  void gluTessBeginPolygon( GLUtesselator *tess,
                void *polygon_data )

Parameters

  tess		Specifies the tessellation object (created with	gluNewTess).

  polygon_data	Specifies a pointer to user polygon data.

Description

  gluTessBeginPolygon and gluTessEndPolygon delimit the	definition of a
  nonconvex polygon. Within each gluTessBeginPolygon/gluTessEndPolygon pair,
  there	must be	one or more calls to gluTessBeginContour/gluTessEndContour.
  Within each contour, there are zero or more calls to gluTessVertex. The
  vertices specify a closed contour (the last vertex of	each contour is
  automatically	linked to the first).  See the gluTessVertex,
  gluTessBeginContour and gluTessEndContour reference pages for	more details.

  polygon_data is a pointer to a user-defined data structure. If the
  appropriate callback(s) are specified	(see gluTessCallback), then this
  pointer is returned to the callback function(s). Thus, it is a convenient
  way to store per-polygon information.

  Once gluTessEndPolygon is called, the	polygon	is tessellated,	and the
  resulting triangles are described through callbacks.	See gluTessCallback
  for descriptions of the callback functions.

Example

  A quadrilateral with a triangular hole in it can be described	like this:

  gluTessBeginPolygon(tobj, NULL);
   gluTessBeginContour(tobj);
     gluTessVertex(tobj, v1, v1);
     gluTessVertex(tobj, v2, v2);
     gluTessVertex(tobj, v3, v3);
     gluTessVertex(tobj, v4, v4);
   gluTessEndContour(tobj);
   gluTessBeginContour(tobj);
     gluTessVertex(tobj, v5, v5);
     gluTessVertex(tobj, v6, v6);
     gluTessVertex(tobj, v7, v7);
   gluTessEndContour(tobj);
  gluTessEndPolygon(tobj);

See Also

  gluNewTess, gluTessBeginContour, gluTessVertex, gluTessCallback,
  gluTessProperty, gluTessNormal gluTessEndPolygon

Introduction | Alphabetic | Specification

Last Edited: Fri, May 26, 1995

AFV


gluTessCallback.3gl

gluTessCallback.3gl (GLU version 1.2 and later)

Name

  gluTessCallback - define a callback for a tessellation object

C Specification

  void gluTessCallback(	GLUtesselator *tess,
            GLenum which,
            void (*fn)() )

Parameters

  tess	 Specifies the tessellation object (created with gluNewTess).

  which	 Specifies the callback	being defined.	The following values are
     valid:	GLU_TESS_BEGIN,	GLU_TESS_BEGIN_DATA, GLU_TESS_EDGE_FLAG,
     GLU_TESS_EDGE_FLAG_DATA, GLU_TESS_VERTEX, GLU_TESS_VERTEX_DATA,
     GLU_TESS_END, GLU_TESS_END_DATA, GLU_TESS_COMBINE,
     GLU_TESS_COMBINE_DATA,	GLU_TESS_ERROR,	and GLU_TESS_ERROR_DATA.

  fn	 Specifies the function	to be called.

Description

  gluTessCallback is used to indicate a	callback to be used by a tessellation
  object.  If the specified callback is	already	defined, then it is replaced.
  If fn	is NULL, then the existing callback becomes undefined.

  These	callbacks are used by the tessellation object to describe how a
  polygon specified by the user	is broken into triangles. Note that there are
  two versions of each callback: one with user-specified polygon data and one
  without. If both versions of a particular callback are specified then	the
  callback with	user-specified polygon data will be used. Note that
  "polygon_data" is a copy of the pointer that was specified when
  gluTessBeginPolygon was called.

  The legal callbacks are as follows:

  GLU_TESS_BEGIN
        The	begin callback is invoked like glBegin to indicate the start
        of a (triangle) primitive. The function takes a single argument
        of type GLenum.  If	the GLU_TESS_BOUNDARY_ONLY property is set to
        GL_FALSE then the argument is set to either	GL_TRIANGLE_FAN,
        GL_TRIANGLE_STRIP, or GL_TRIANGLES.	If the GLU_TESS_BOUNDARY_ONLY
        property is	set to GL_TRUE then the	argument will be set to
        GL_LINE_LOOP. The function prototype for this callback looks
        like:

        void begin ( GLenum	type );

  GLU_TESS_BEGIN_DATA
        The	same as	the GLU_TESS_BEGIN callback except that	it takes an
        additional pointer argument. This pointer is identical to the
        opaque pointer provided when gluTessBeginPolygon was called. The
        function prototype for this	callback looks like:

        void beginData ( GLenum type, void *polygon_data );

  GLU_TESS_EDGE_FLAG
        The	edge flag callback is similar to glEdgeFlag.  The function
        takes a single Boolean flag	that indicates which edges lie on the
        polygon boundary. If the flag is GL_TRUE, then each	vertex that
        follows begins an edge which lies on the polygon boundary -- that
        is,	an edge	which separates	an interior region from	an exterior
        one.  If the flag is GL_FALSE, then	each vertex that follows
        begins an edge which lies in the polygon interior. The edge	flag
        callback (if defined) is invoked before the	first vertex callback
        is made.

        Since triangle fans	and triangle strips do not support edge
        flags, the begin callback is not called with GL_TRIANGLE_FAN or
        GL_TRIANGLE_STRIP if an edge flag callback is provided.  Instead,
        the	fans and strips	are converted to independent triangles.	The
        function prototype for this	callback looks like:

        void edgeFlag ( GLboolean flag );

  GLU_TESS_EDGE_FLAG_DATA
        The	same as	the GLU_TESS_EDGE_FLAG callback	except that it takes
        an additional pointer argument. This pointer is identical to the
        opaque pointer provided when gluTessBeginPolygon was called. The
        function prototype for this	callback looks like:

        void edgeFlagData (	GLboolean flag,	void *polygon_data );

  GLU_TESS_VERTEX
        The	vertex callback	is invoked between the begin and end
        callbacks.	It is similar to glVertex, and it defines the
        vertices of	the triangles created by the tessellation process.
        The	function takes a pointer as its	only argument.	This pointer
        is identical to the	opaque pointer provided	by the user when the
        vertex was described (see gluTessVertex). The function prototype
        for	this callback looks like:

        void vertex	( void *vertex_data );

  GLU_TESS_VERTEX_DATA
        The	same as	the GLU_TESS_VERTEX callback except that it takes an
        additional pointer argument. This pointer is identical to the
        opaque pointer provided when gluTessBeginPolygon was called. The
        function prototype for this	callback looks like:

        void vertexData ( void *vertex_data, void *polygon_data );

  GLU_TESS_END
        The	end callback serves the	same purpose as	glEnd. It indicates
        the	end of a primitive and it takes	no arguments. The function
        prototype for this callback	looks like:

        void end ( void );

  GLU_TESS_END_DATA
        The	same as	the GLU_TESS_END callback except that it takes an
        additional pointer argument. This pointer is identical to the
        opaque pointer provided when gluTessBeginPolygon was called. The
        function prototype for this	callback looks like:
        void endData ( void	*polygon_data);

  GLU_TESS_COMBINE
        The	combine	callback is called to create a new vertex when the
        tessellation detects an intersection, or wishes to merge
        features. The function takes four arguments: an array of three
        elements each of type GLdouble, an array of	four pointers, an
        array of four elements each	of type	GLfloat, and a pointer to a
        pointer. The prototype looks like:
        void combine( GLdouble coords[3], void *vertex_data[4],
              GLfloat weight[4], void **outData );

        The	vertex is defined as a linear combination of up	to 4 existing
        vertices, stored in	vertex_data. The coefficients of the linear
        combination	are given by weight; these weights always sum to 1.0.
        All	vertex pointers	are valid even when some of the	weights	are
        zero.  coords gives	the location of	the new	vertex.

        The	user must allocate another vertex, interpolate parameters
        using vertex_data and weight, and return the new vertex pointer
        in outData.	 This handle is	supplied during	rendering callbacks.
        The	user is	responsible for	freeing	the memory sometime after
        gluTessEndPolygon is called.

        For	example, if the	polygon	lies in	an arbitrary plane in 3-space,
        and we associate a color with each vertex, the GLU_TESS_COMBINE
        callback might look like this:

        void myCombine( GLdouble coords[3],	VERTEX *d[4],
                GLfloat w[4], VERTEX **dataOut )
            {
           VERTEX *new = new_vertex();

           new->x = coords[0];
           new->y = coords[1];
           new->z = coords[2];
           new->r = w[0]*d[0]->r + w[1]*d[1]->r + w[2]*d[2]->r + w[3]*d[3]->r;
           new->g = w[0]*d[0]->g + w[1]*d[1]->g + w[2]*d[2]->g + w[3]*d[3]->g;
           new->b = w[0]*d[0]->b + w[1]*d[1]->b + w[2]*d[2]->b + w[3]*d[3]->b;
           new->a = w[0]*d[0]->a + w[1]*d[1]->a + w[2]*d[2]->a + w[3]*d[3]->a;
           *dataOut	= new;
            }

        If the tessellation	detects	an intersection, then the
        GLU_TESS_COMBINE or	GLU_TESS_COMBINE_DATA callback (see below)
        must be defined, and it must write a non-NULL pointer into
        dataOut. Otherwise the GLU_TESS_NEED_COMBINE_CALLBACK error
        occurs, and	no output is generated.	 (This is the only error that
        can	occur during tessellation and rendering.)

  GLU_TESS_COMBINE_DATA
        The	same as	the GLU_TESS_COMBINE callback except that it takes an
        additional pointer argument. This pointer is identical to the
        opaque pointer provided when gluTessBeginPolygon was called. The
        function prototype for this	callback looks like:

        void combineData ( GLdouble	coords[3], void	*vertex_data[4],
                   GLfloat weight[4], void **outData,
                   void *polygon_data );

  GLU_TESS_ERROR
        The	error callback is called when an error is encountered.	The
        one	argument is of type GLenum; it indicates the specific error
        that occurred and will be set to one of
        GLU_TESS_MISSING_BEGIN_POLYGON, GLU_TESS_MISSING_END_POLYGON,
        GLU_TESS_MISSING_BEGIN_CONTOUR, GLU_TESS_MISSING_END_CONTOUR,
        GLU_TESS_COORD_TOO_LARGE, GLU_TESS_NEED_COMBINE_CALLBACK.
        Character strings describing these errors can be retrieved with
        the	gluErrorString call. The function prototype for	this callback
        looks like:

        void error ( GLenum	errno );

        The	GLU library will recover from the first	four errors by
        inserting the missing call(s).  GLU_TESS_COORD_TOO_LARGE says
        that some vertex coordinate	exceeded the predefined	constant
        GLU_TESS_MAX_COORD in absolute value, and that the value has been
        clamped.  (Coordinate values must be small enough so that two can
        be multiplied together without overflow.)
        GLU_TESS_NEED_COMBINE_CALLBACK says	that the tessellation
        detected an	intersection between two edges in the input data, and
        the	GLU_TESS_COMBINE or GLU_TESS_COMBINE_DATA callback was not
        provided.  No output will be generated.

  GLU_TESS_ERROR_DATA
        The	same as	the GLU_TESS_ERROR callback except that	it takes an
        additional pointer argument. This pointer is identical to the
        opaque pointer provided when gluTessBeginPolygon was called. The
        function prototype for this	callback looks like:

        void errorData ( GLenum errno, void	*polygon_data );

Example

  Polygons tessellated can be rendered directly	like this:

  gluTessCallback(tobj, GLU_TESS_BEGIN, glBegin);
  gluTessCallback(tobj, GLU_TESS_VERTEX, glVertex3dv);
  gluTessCallback(tobj, GLU_TESS_END, glEnd);
  gluTessCallback(tobj, GLU_TESS_COMBINE, myCombine);
  gluTessBeginPolygon(tobj, NULL);
    gluTessBeginContour(tobj);
      gluTessVertex(tobj, v, v);
      ...
    gluTessEndContour(tobj);
  gluTessEndPolygon(tobj);

  Typically, the tessellated polygon should be stored in a display list	so
  that it does not need	to be retessellated every time it is rendered.

See Also

  glBegin, glEdgeFlag, glVertex, gluNewTess, gluErrorString, gluTessVertex,
  gluTessBeginPolygon, gluTessBeginContour, gluTessProperty, gluTessNormal

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluTessCallback.3gl

gluTessCallback.3gl (GLU versions 1.0 and 1.1)

Name

  gluTessCallback - define a callback for a tessellation object

C Specification

  void gluTessCallback(	GLUtriangulatorObj *tobj,
            GLenum which,
            void (*fn)( )

Parameters

  tobj	 Specifies the tessellation object (created with gluNewTess).

  which	 Specifies the callback	being defined.	The following values are
     valid:	GLU_BEGIN, GLU_EDGE_FLAG, GLU_VERTEX, GLU_END, and GLU_ERROR.

  fn	 Specifies the function	to be called.

Description

  gluTessCallback is used to indicate a	callback to be used by a tessellation
  object.  If the specified callback is	already	defined, then it is replaced.
  If fn	is NULL, then the existing callback is erased.

  These	callbacks are used by the tessellation object to describe how a
  polygon specified by the user	is broken into triangles.

  The legal callbacks are as follows:

  GLU_BEGIN The	begin callback is invoked like glBegin to indicate the start
        of a (triangle) primitive.	The function takes a single argument
        of type GLenum that	is either GL_TRIANGLE_FAN, GL_TRIANGLE_STRIP,
        or GL_TRIANGLES.

  GLU_EDGE_FLAG
        The	edge flag callback is similar to glEdgeFlag.  The function
        takes a single Boolean flag	that indicates which edges of the
        created triangles were part	of the original	polygon	defined	by
        the	user, and which	were created by	the tessellation process.  If
        the	flag is	GL_TRUE, then each vertex that follows begins an edge
        that was part of the original polygon.  If the flag	is GL_FALSE,
        then each vertex that follows begins an edge that was generated
        by the tessellator.	 The edge flag callback	(if defined) is
        invoked before the first vertex callback is	made.

        Since triangle fans	and triangle strips do not support edge
        flags, the begin callback is not called with GL_TRIANGLE_FAN or
        GL_TRIANGLE_STRIP if an edge flag callback is provided.  Instead,
        the	fans and strips	are converted to independent triangles.

  GLU_VERTEX
        The	vertex callback	is invoked between the begin and end
        callbacks.	It is similar to glVertex, and it defines the
        vertices of	the triangles created by the tessellation process.
        The	function takes a pointer as its	only argument.	This pointer
        is identical to the	opaque pointer provided	by the user when the
        vertex was described (see gluTessVertex).

  GLU_END   The	end callback serves the	same purpose as	glEnd. It indicates
        the	end of a primitive and it takes	no arguments.

  GLU_ERROR The	error callback is called when an error is encountered.	The
        one	argument is of type GLenum, and	it indicates the specific
        error that occurred.  There	are eight errors unique	to polygon
        tessellation, named	GLU_TESS_ERROR1	through	GLU_TESS_ERROR8.
        Character strings describing these errors can be retrieved with
        the	gluErrorString call.

Example

  Polygons tessellated can be rendered directly	like this:

  gluTessCallback(tobj, GLU_BEGIN, glBegin);
  gluTessCallback(tobj, GLU_VERTEX, glVertex3dv);
  gluTessCallback(tobj, GLU_END, glEnd);

  gluBeginPolygon(tobj);
     gluTessVertex(tobj, v, v);
     ...
  gluEndPolygon(tobj);

  Typically, the tessellated polygon should be stored in a display list	so
  that it does not need	to be retessellated every time it is rendered.

See Also

  glBegin, glEdgeFlag, glVertex, gluDeleteTess,	gluErrorString,	gluNewTess,
  gluTessVertex

Introduction | Alphabetic | Specification

Last Edited: Fri, May 26, 1995

AFV


gluTessEndPolygon.3gl

gluTessEndPolygon.3gl (GLU version 1.2 and later)

Name

  gluTessEndPolygon - delimit a	polygon	description

C Specification

  void gluTessEndPolygon( GLUtesselator	*tess )

Parameters

  tess	Specifies the tessellation object (created with	gluNewTess).

Description

  gluTessBeginPolygon and gluTessEndPolygon delimit the	definition of a
  nonconvex polygon. Within each gluTessBeginPolygon/gluTessEndPolygon pair,
  there	must be	one or more calls to gluTessBeginContour/gluTessEndContour.
  Within each contour, there are zero or more calls to gluTessVertex. The
  vertices specify a closed contour (the last vertex of	each contour is
  automatically	linked to the first).  See the gluTessVertex,
  gluTessBeginContour and gluTessEndContour reference pages for	more details.

  Once gluTessEndPolygon is called, the	polygon	is tessellated,	and the
  resulting triangles are described through callbacks.	See gluTessCallback
  for descriptions of the callback functions.

Example

  A quadrilateral with a triangular hole in it can be described	like this:

  gluTessBeginPolygon(tobj, NULL);
   gluTessBeginContour(tobj);
     gluTessVertex(tobj, v1, v1);
     gluTessVertex(tobj, v2, v2);
     gluTessVertex(tobj, v3, v3);
     gluTessVertex(tobj, v4, v4);
   gluTessEndContour(tobj);
   gluTessBeginContour(tobj);
     gluTessVertex(tobj, v5, v5);
     gluTessVertex(tobj, v6, v6);
     gluTessVertex(tobj, v7, v7);
   gluTessEndContour(tobj);
  gluTessEndPolygon(tobj);

See Also

  gluNewTess, gluTessBeginContour, gluTessVertex, gluTessCallback,
  gluTessProperty, gluTessNormal, gluTessBeginPolygon

Introduction | Alphabetic | Specification

Last Edited: Fri, May 26, 1995

AFV


gluTessNormal.3gl

gluTessNormal.3gl (GLU version 1.2 and later)

Name

  gluTessNormal	- specify a normal for a polygon

C Specification

  void gluTessNormal( GLUtesselator *tess,
              GLdouble x,
              GLdouble y,
              GLdouble z )

Parameters

  tess	Specifies the tessellation object (created with	gluNewTess).

  x	Specifies the first component of the normal.

  y	Specifies the second component of the normal.

  z	Specifies the third component of the normal.

Description

  gluTessNormal	describes a normal for a polygon that the user is defining.
  All input data will be projected onto	a plane	perpendicular to one of	the
  three	coordinate axes	before tessellation and	all output triangles will be
  oriented CCW with respect to the normal (CW orientation can be obtained by
  reversing the	sign of	the supplied normal). For example, if you know that
  all polygons lie in the x-y plane, call gluTessNormal(tess, 0.0, 0.0,	1.0)
  before rendering any polygons.

  If the supplied normal is (0,0,0) (the default value), the normal is
  determined as	follows. The direction of the normal, up to its	sign, is
  found	by fitting a plane to the vertices, without regard to how the
  vertices are connected. It is	expected that the input	data lies
  approximately	in the plane; otherwise	projection perpendicular to one	of
  the three coordinate axes may	substantially change the geometry. The sign
  of the normal	is chosen so that the sum of the signed	areas of all input
  contours is non-negative (where a CCW	contour	has positive area).

  The supplied normal persists until it	is changed by another call to
  gluTessNormal.

See Also

  gluTessBeginPolygon, gluTessEndPolygon

Introduction | Alphabetic | Specification

Last Edited: Fri, May 26, 1995

AFV


gluTessProperty.3gl

gluTessProperty.3gl (GLU version 1.2 and later)

Name

  gluTessProperty - set	a tessellation object property

C Specification

  void gluTessProperty(	GLUtesselator *tess,
            GLenum which,
            GLdouble value )

Parameters

  tess	 Specifies the tessellation object (created with gluNewTess).

  which	 Specifies the property	to be set.  Valid values are
     GLU_TESS_WINDING_RULE,	GLU_TESS_BOUNDARY_ONLY,	GLU_TESS_TOLERANCE.

  value	 Specifies the value of	the indicated property.

Description

  gluTessProperty is used to control properties	stored in a tessellation
  object.  These properties affect the way that	the polygons are interpreted
  and rendered.	The legal values for which are as follows:

  GLU_TESS_WINDING_RULE	   determines which parts of the polygon are on	the
               "interior". value may be set	to one of
               GLU_TESS_WINDING_ODD, GLU_TESS_WINDING_NONZERO,
               GLU_TESS_WINDING_POSITIVE, or
               GLU_TESS_WINDING_NEGATIVE, or
               GLU_TESS_WINDING_ABS_GEQ_TWO.

               To understand how the winding rule works first
               consider that the input contours partition the
               plane into regions. The winding rule	determines
               which of these regions are inside the polygon.

               For a single	contour	C, the winding number of a
               point x is simply the signed	number of revolutions
               we make around x as we travel once around C (where
               CCW is positive). When there	are several contours,
               the individual winding numbers are summed. This
               procedure associates	a signed integer value with
               each	point x	in the plane. Note that	the winding
               number is the same for all points in	a single
               region.

               The winding rule classifies a region	as "inside"
               if its winding number belongs to the	chosen
               category (odd, nonzero, positive, negative, or
               absolute value of at	least two). The	previous GLU
               tessellator (prior to GLU 1.2) used the "odd"
               rule. The "nonzero" rule is another common way to
               define the interior.	The other three	rules are
               useful for polygon CSG operations.

  GLU_TESS_BOUNDARY_ONLY   is a	boolean	value ("value" should be set to
               GL_TRUE or GL_FALSE). When set to GL_TRUE, a	set
               of closed contours separating the polygon interior
               and exterior	are returned instead of	a
               tessellation. Exterior contours are oriented	CCW
               with	respect	to the normal, interior	contours are
               oriented CW.	The GLU_TESS_BEGIN and
               GLU_TESS_BEGIN_DATA callbacks use the type
               GL_LINE_LOOP	for each contour.

  GLU_TESS_TOLERANCE	   specifies a tolerance for merging features to
               reduce the size of the output.  For example,	two
               vertices which are very close to each other might
               be replaced by a single vertex. The tolerance is
               multiplied by the largest coordinate	magnitude of
               any input vertex; this specifies the	maximum
               distance that any feature can move as the result
               of a	single merge operation.	If a single feature
               takes part in several merge operations, the total
               distance moved could	be larger.

               Feature merging is completely optional; the
               tolerance is	only a hint.  The implementation is
               free	to merge in some cases and not in others, or
               to never merge features at all. The default
               tolerance is	zero.

               The current implementation merges vertices only if
               they	are exactly coincident,	regardless of the
               current tolerance. A	vertex is spliced into an
               edge	only if	the implementation is unable to
               distinguish which side of the edge the vertex lies
               on. Two edges are merged only when both endpoints
               are identical.

See Also

  gluGetTessProperty

Introduction | Alphabetic | Specification

Last Edited: Fri, May 26, 1995

AFV


gluTessVertex.3gl

gluTessVertex.3gl (GLU version 1.2 and later)

Name

  gluTessVertex	- specify a vertex on a	polygon

C Specification

  void gluTessVertex( GLUtesselator *tess,
              GLdouble coords[3],
              void *data )

Parameters

  tess	  Specifies the	tessellation object (created with gluNewTess).

  coords  Specifies the	location of the	vertex.

  data	  Specifies an opaque pointer passed back to the user with the vertex
      callback (as specified by gluTessCallback).

Description

  gluTessVertex	describes a vertex on a	polygon	that the user is defining.
  Successive gluTessVertex calls describe a closed contour.  For example, if
  the user wants to describe a quadrilateral, then gluTessVertex should	be
  called four times.  gluTessVertex can	only be	called between
  gluTessBeginContour and gluTessEndContour.

  data normally	points to a structure containing the vertex location, as well
  as other per-vertex attributes such as color and normal.  This pointer is
  passed back to the user through the GLU_TESS_VERTEX or GLU_TESS_VERTEX_DATA
  callback after tessellation (see the gluTessCallback reference page).

Example

  A quadrilateral with a triangular hole in it can be described	as follows:

  gluTessBeginPolygon(tobj, NULL);
   gluTessBeginContour(tobj);
     gluTessVertex(tobj, v1, v1);
     gluTessVertex(tobj, v2, v2);
     gluTessVertex(tobj, v3, v3);
     gluTessVertex(tobj, v4, v4);
   gluTessEndContour(tobj);
   gluTessBeginContour(tobj);
     gluTessVertex(tobj, v5, v5);
     gluTessVertex(tobj, v6, v6);
     gluTessVertex(tobj, v7, v7);
   gluTessEndContour(tobj); gluTessEndPolygon(tobj);

See Also

  gluTessBeginPolygon, gluNewTess, gluTessBeginContour,	gluTessCallback,
  gluTessProperty, gluTessNormal, gluTessEndPolygon

Introduction | Alphabetic | Specification

Last Edited: Fri, May 26, 1995

AFV


gluTessVertex.3gl

gluTessVertex.3gl (GLU versions 1.0 and 1.1)

Name

  gluTessVertex	- specify a vertex on a	polygon

C Specification

  void gluTessVertex( GLUtriangulatorObj *tobj,
              GLdouble v[3],
              void *data )

Parameters

  tobj	Specifies the tessellation object (created with	gluNewTess).

  v	Specifies the location of the vertex.

  data	Specifies an opaque pointer passed back	to the user with the vertex
    callback (as specified by gluTessCallback).

Description

  gluTessVertex	describes a vertex on a	polygon	that the user is defining.
  Successive gluTessVertex calls describe a closed contour.  For example, if
  the user wants to describe a quadrilateral, then gluTessVertex should	be
  called four times.  gluTessVertex can	only be	called between
  gluBeginPolygon and gluEndPolygon.

  data normally	points to a structure containing the vertex location, as well
  as other per-vertex attributes such as color and normal.  This pointer is
  passed back to the user through the GLU_VERTEX callback after	tessellation
  (see the gluTessCallback reference page).

Example

  A quadrilateral with a triangular hole in it can be described	as follows:

  gluBeginPolygon(tobj);
     gluTessVertex(tobj, v1, v1);
     gluTessVertex(tobj, v2, v2);
     gluTessVertex(tobj, v3, v3);
     gluTessVertex(tobj, v4, v4);
  gluNextContour(tobj, GLU_INTERIOR);
     gluTessVertex(tobj, v5, v5);
     gluTessVertex(tobj, v6, v6);
     gluTessVertex(tobj, v7, v7);
  gluEndPolygon(tobj);

See Also

  gluBeginPolygon, gluNewTess, gluNextContour, gluTessCallback


gluUnProject.3gl

Name

  gluUnProject - map window coordinates	to object coordinates

C Specification

  int gluUnProject( GLdouble winx,
            GLdouble winy,
            GLdouble winz,
            const GLdouble modelMatrix[16],
            const GLdouble projMatrix[16],
            const GLint	viewport[4],
            GLdouble *objx,
            GLdouble *objy,
            GLdouble *objz )

Parameters

  winx,	winy, winz
          Specify the window coordinates to be mapped.

  modelMatrix	  Specifies the	modelview matrix (as from a glGetDoublev
          call).

  projMatrix	  Specifies the	projection matrix (as from a glGetDoublev
          call).

  viewport	  Specifies the	viewport (as from a glGetIntegerv call).

  objx,	objy, objz
          Returns the computed object coordinates.

Description

  gluUnProject maps the	specified window coordinates into object coordinates
  using	modelMatrix, projMatrix, and viewport.	The result is stored in	objx,
  objy,	and objz.  A return value of GL_TRUE indicates success,	and GL_FALSE
  indicates failure.

See Also

  glGet, gluProject

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glVertex.3gl

Name

  glVertex2d, glVertex2f, glVertex2i, glVertex2s, glVertex3d, glVertex3f,
  glVertex3i, glVertex3s, glVertex4d, glVertex4f, glVertex4i, glVertex4s,
  glVertex2dv, glVertex2fv, glVertex2iv, glVertex2sv, glVertex3dv,
  glVertex3fv, glVertex3iv, glVertex3sv, glVertex4dv, glVertex4fv,
  glVertex4iv, glVertex4sv - specify a vertex

C Specification

  void glVertex2d( GLdouble x,
           GLdouble y )
  void glVertex2f( GLfloat x,
           GLfloat y )
  void glVertex2i( GLint x,
           GLint y )
  void glVertex2s( GLshort x,
           GLshort y )
  void glVertex3d( GLdouble x,
           GLdouble y,
           GLdouble z )
  void glVertex3f( GLfloat x,
           GLfloat y,
           GLfloat z )
  void glVertex3i( GLint x,
           GLint y,
           GLint z )
  void glVertex3s( GLshort x,
           GLshort y,
           GLshort z )
  void glVertex4d( GLdouble x,
           GLdouble y,
           GLdouble z,
           GLdouble w )
  void glVertex4f( GLfloat x,
           GLfloat y,
           GLfloat z,
           GLfloat w )
  void glVertex4i( GLint x,
           GLint y,
           GLint z,
           GLint w )
  void glVertex4s( GLshort x,
           GLshort y,
           GLshort z,
           GLshort w )

Parameters

  x, y,	z, w
       Specify x, y, z, and	w coordinates of a vertex.  Not	all
       parameters are present in all forms of the command.

C Specification

  void glVertex2dv( const GLdouble *v )
  void glVertex2fv( const GLfloat *v )
  void glVertex2iv( const GLint	*v )
  void glVertex2sv( const GLshort *v )
  void glVertex3dv( const GLdouble *v )
  void glVertex3fv( const GLfloat *v )
  void glVertex3iv( const GLint	*v )
  void glVertex3sv( const GLshort *v )
  void glVertex4dv( const GLdouble *v )
  void glVertex4fv( const GLfloat *v )
  void glVertex4iv( const GLint	*v )
  void glVertex4sv( const GLshort *v )

Parameters

  v    Specifies a pointer to an array of two, three, or four elements.	 The
       elements	of a two-element array are x and y; of a three-element array,
       x, y, and z; and	of a four-element array, x, y, z, and w.

Description

  glVertex commands are	used within glBegin/glEnd pairs	to specify point,
  line,	and polygon vertices.  The current color, normal, and texture
  coordinates are associated with the vertex when glVertex is called.

  When only x and y are	specified, z defaults to 0.0 and w defaults to 1.0.
  When x, y, and z are specified, w defaults to	1.0.

Notes

  Invoking glVertex outside of a glBegin/glEnd pair results in undefined
  behavior.

See Also

  glBegin, glCallList, glColor,	glEdgeFlag, glEvalCoord, glIndex, glMaterial,
  glNormal, glRect, glTexCoord

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glVertexPointerEXT.3gl

Name

  glVertexPointerEXT - define an array of vertex data

C Specification

  void glVertexPointerEXT( GLint size,
               GLenum type,
               GLsizei stride,
               GLsizei count,
               const GLvoid	*pointer )

Parameters

  size	   Specifies the number	of coordinates per vertex, must	be 2,3,	or 4.

  type	   Specifies the data type of each coordinate in the array.  Symbolic
       constants GL_SHORT, GL_INT, GL_FLOAT, or GL_DOUBLE_EXT are
       accepted.

  stride   Specifies the byte offset between consecutive vertexes. If stride
       is 0	the vertexes are understood to be tightly packed in the
       array.

  count	   Specifies the number	of vertexes, counting from the first, that
       are static.

  pointer  Specifies a pointer to the first coordinate of the first vertex in
       the array.

Description

  glVertexPointerEXT specifies the location and data format of an array of
  vertex coordinates to use when rendering using the vertex array extension.
  size specifies the number of coordinates per vertex and type the
  data type of the coordinates. stride gives the byte stride from one vertex
  to the next allowing vertexes and attributes to be packed into a single
  array or stored in separate arrays. (Single-array storage may be more
  efficient on some implementations.) count indicates the number of array
  elements (counting from the first) that are static. Static elements may be
  modified by the application, but once they are modified, the application
  must explicitly respecify the array before using it for any rendering. When
  a vertex array is specified, size, type, stride, count, and pointer are saved
  as client-side state, and static array elements may be cached by the
  implementation.

  The vertex array is enabled and disabled using glEnable and glDisable	with
  the argument GL_VERTEX_ARRAY_EXT. If enabled,	the vertex array is used when
  glDrawArraysEXT or glArrayElementEXT is called.

  Use glDrawArraysEXT to define	a sequence of primitives (all of the same
  type)	from pre-specified vertex and vertex attribute arrays.	Use
  glArrayElementEXT to specify primitives by indexing vertexes and vertex
  attributes.

Notes

  Non-static array elements are	not accessed until glArrayElementEXT or
  glDrawArraysEXT is executed.

  By default the vertex	array is disabled and it won't be accessed when
  glArrayElementEXT or glDrawArraysEXT is called.

  Although it is not an error to call glVertexPointerEXT between the
  execution of glBegin and the corresponding execution of glEnd, the results
  are undefined.

  glVertexPointerEXT will typically be implemented on the client side with no
  protocol.

  Since	the vertex array parameters are	client side state, they	are not	saved
  or restored by glPushAttrib and glPopAttrib.

  glVertexPointerEXT commands are not entered into display lists.

  glVertexPointerEXT is	part of	the EXT_vertex_array extension, not
  part of the core GL command set. If "GL_EXT_vertex_array" is
  included in the string returned by glGetString, when called with argument
  GL_EXTENSIONS, extension EXT_vertex_array is supported.

Errors

  GL_INVALID_VALUE is generated	if size	is not 2, 3, or	4.

  GL_INVALID_ENUM is generated if type is is not an accepted value.

  GL_INVALID_VALUE is generated	if stride or count is negative.

Associated Gets

  glIsEnabled with argument GL_VERTEX_ARRAY_EXT
  glGet	with argument GL_VERTEX_ARRAY_SIZE_EXT
  glGet	with argument GL_VERTEX_ARRAY_TYPE_EXT
  glGet	with argument GL_VERTEX_ARRAY_STRIDE_EXT
  glGet	with argument GL_VERTEX_ARRAY_COUNT_EXT
  glGetPointervEXT with	argument GL_VERTEX_ARRAY_POINTER_EXT

See Also

  glArrayElementEXT, glColorPointerEXT,	glDrawArraysEXT,
  glEdgeFlagPointerEXT,	glGetPointervEXT, glIndexPointerEXT,
  glNormalPointerEXT, glTexCoordPointerEXT, glEnable

Introduction | Alphabetic | Specification

Last Edited: Fri Feb 24, 1995

AFV


glViewport.3gl

Name

  glViewport - set the viewport

C Specification

  void glViewport( GLint x,
           GLint y,
           GLsizei width,
           GLsizei height )

Parameters

  x, y Specify the lower left corner of	the viewport rectangle,	in pixels.
       The default is (0,0).

  width, height
       Specify the width and height, respectively, of the viewport.  When a
       GL context is first attached to a window, width and height are set to
       the dimensions of that window.

Description

  glViewport specifies the affine transformation of x and y from normalized
  device coordinates to	window coordinates.  Let (x  , y  ) be normalized
                                                   nd   nd
  device coordinates.  Then the	window coordinates x , y ) are computed as
  follows:					    w	w

                                        width
                x  = (x  +1)----- + x
                 w	   nd	  2

                                        height
                y  = (y  +1)------ + y
                 w	   nd	   2

  Viewport width and height are	silently clamped to a range that depends on
  the implementation.  This range is queried by	calling	glGet with argument
  GL_MAX_VIEWPORT_DIMS.

Errors

  GL_INVALID_VALUE is generated	if either width	or height is negative.

  GL_INVALID_OPERATION is generated if glViewport is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_VIEWPORT
  glGet	with argument GL_MAX_VIEWPORT_DIMS

See Also

  glDepthRange

Introduction | Alphabetic | Specification

Last Edited: Fri, May 26, 1995

AFV


glXChooseVisual.3gl

Name

  glXChooseVisual - return a visual that matches specified attributes

C Specification

  XVisualInfo* glXChooseVisual(	Display	*dpy,
                int screen,
                int *attribList	)

Parameters

  dpy	      Specifies	the connection to the X	server.

  screen      Specifies	the screen number.

  attribList  Specifies	a list of Boolean attributes and integer
          attribute/value pairs.  The last attribute must be None.

Description

  glXChooseVisual returns a pointer to an XVisualInfo structure	describing
  the visual that best meets a minimum specification.  The Boolean GLX
  attributes of	the visual that	is returned will match the specified values,
  and the integer GLX attributes will meet or exceed the specified minimum
  values.  If all other	attributes are equivalent, then	TrueColor and
  PseudoColor visuals have priority over DirectColor and StaticColor visuals,
  respectively.	 If no conforming visual exists, NULL is returned.  To free
  the data returned by this function, use XFree.

  All Boolean GLX attributes default to	False except GLX_USE_GL, which
  defaults to True.  All integer GLX attributes	default	to zero.  Default
  specifications are superseded	by attributes included in attribList.
  Boolean attributes included in attribList are	understood to be True.
  Integer attributes are followed immediately by the corresponding desired or
  minimum value.  The list must	be terminated with None.

  The interpretations of the various GLX visual	attributes are as follows:

  GLX_USE_GL		Ignored.  Only visuals that can	be rendered with GLX
            are considered.

  GLX_BUFFER_SIZE	Must be	followed by a nonnegative integer that
            indicates the desired color index buffer size.	The
            smallest index buffer of at least the specified	size
            is preferred.  Ignored if GLX_RGBA is asserted.

  GLX_LEVEL		Must be	followed by an integer buffer-level
            specification.	This specification is honored
            exactly.  Buffer level zero corresponds	to the
            default	frame buffer of	the display.  Buffer level
            one is the first overlay frame buffer, level two the
            second overlay frame buffer, and so on.	 Negative
            buffer levels correspond to underlay frame buffers.

  GLX_RGBA		If present, only TrueColor and DirectColor visuals
            are considered.	 Otherwise, only PseudoColor and
            StaticColor visuals are	considered.

  GLX_DOUBLEBUFFER	If present, only double-buffered visuals are
            considered.   Otherwise, only single-buffered visuals
            are considered.

  GLX_STEREO		If present, only stereo	visuals	are considered.
            Otherwise, only	monoscopic visuals are considered.

  GLX_AUX_BUFFERS	Must be	followed by a nonnegative integer that
            indicates the desired number of	auxiliary buffers.
            Visuals	with the  smallest number of auxiliary
            buffers	that meets or exceeds the specified number
            are preferred.

  GLX_RED_SIZE		Must be	followed by a nonnegative minimum size
            specification.	If this	value is zero, the smallest
            available red buffer is	preferred.  Otherwise, the
            largest	available red buffer of	at least the minimum
            size is	preferred.

  GLX_GREEN_SIZE	Must be	followed by a nonnegative minimum size
            specification.	If this	value is zero, the smallest
            available green	buffer is preferred.  Otherwise, the
            largest	available green	buffer of at least the
            minimum	size is	preferred.

  GLX_BLUE_SIZE		Must be	followed by a nonnegative minimum size
            specification.	If this	value is zero, the smallest
            available blue buffer is preferred.  Otherwise,	the
            largest	available blue buffer of at least the minimum
            size is	preferred.

  GLX_ALPHA_SIZE	Must be	followed by a nonnegative minimum size
            specification.	If this	value is zero, the smallest
            available alpha	buffer is preferred.  Otherwise, the
            largest	available alpha	buffer of at least the
            minimum	size is	preferred.

  GLX_DEPTH_SIZE	Must be	followed by a nonnegative minimum size
            specification.	If this	value is zero, visuals with
            no depth buffer	are preferred.	Otherwise, the
            largest	available depth	buffer of at least the
            minimum	size is	preferred.

  GLX_STENCIL_SIZE	Must be	followed by a nonnegative integer that
            indicates the desired number of	stencil	bitplanes.
            The smallest stencil buffer of at least	the specified
            size is	preferred.  If the desired value is zero,
            visuals	with no	stencil	buffer are preferred.

  GLX_ACCUM_RED_SIZE	Must be	followed by a nonnegative minimum size
            specification.	If this	value is zero, visuals with
            no red accumulation buffer are preferred.  Otherwise,
            the largest possible red accumulation buffer of	at
            least the minimum size is preferred.

  GLX_ACCUM_GREEN_SIZE	Must be	followed by a nonnegative minimum size
            specification.	If this	value is zero, visuals with
            no green accumulation buffer are preferred.
            Otherwise, the largest possible	green accumulation
            buffer of at least the minimum size is preferred.

  GLX_ACCUM_BLUE_SIZE	Must be	followed by a nonnegative minimum size
            specification.	If this	value is zero, visuals with
            no blue	accumulation buffer are	preferred.
            Otherwise, the largest possible	blue accumulation
            buffer of at least the minimum size is preferred.

  GLX_ACCUM_ALPHA_SIZE	Must be	followed by a nonnegative minimum size
            specification.	If this	value is zero, visuals with
            no alpha accumulation buffer are preferred.
            Otherwise, the largest possible	alpha accumulation
            buffer of at least the minimum size is preferred.

Examples

  attribList =
          {GLX_RGBA, GLX_RED_SIZE, 4, GLX_GREEN_SIZE, 4, GLX_BLUE_SIZE,
          4, None};

  Specifies a single-buffered RGB visual in the	normal frame buffer, not an
  overlay or underlay buffer.  The returned visual supports at least four
  bits each of red, green, and blue, and possibly no bits of alpha.  It	does
  not support color index mode,	double-buffering, or stereo display.  It may
  or may not have one or more auxiliary	color buffers, a depth buffer, a
  stencil buffer, or an	accumulation buffer.

Notes

  XVisualInfo is defined in Xutil.h.  It is a structure	that includes visual,
  visualID, screen, and	depth elements.

  glXChooseVisual is implemented as a client-side utility using	only
  XGetVisualInfo and glXGetConfig.  Calls to these two routines	can be used
  to implement selection algorithms other than the generic one implemented by
  glXChooseVisual.

  GLX implementers are strongly	discouraged, but not proscribed, from
  changing the selection algorithm used	by glXChooseVisual.  Therefore,
  selections may change	from release to	release	of the client-side library.

  There	is no direct filter for	picking	only visuals that support GLXPixmaps.
  GLXPixmaps are supported for visuals whose GLX_BUFFER_SIZE.  is one of the
  Pixmap depths	supported by the X server.

Errors

  NULL is returned if an undefined GLX attribute is encountered	in
  attribList.

See Also

  glXCreateContext, glXGetConfig

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glXCopyContext.3gl

Name

  glXCopyContext - copy	state from one rendering context to another

C Specification

  void glXCopyContext( Display *dpy,
               GLXContext src,
               GLXContext dst,
               GLuint mask )

Parameters

  dpy	Specifies the connection to the	X server.

  src	Specifies the source context.

  dst	Specifies the destination context.

  mask	Specifies which	portions of src	state are to be	copied to dst.

Description

  glXCopyContext copies	selected groups	of state variables from	src to dst.
  mask indicates which groups of state variables are to	be copied.  mask
  contains the bitwise OR of the same symbolic names that are passed to	the
  OpenGL command glPushAttrib.	The single symbolic constant
  GL_ALL_ATTRIB_BITS can be used to copy the maximum possible portion of
  rendering state.

  The copy can be done only if the renderers named by src and dst share	an
  address space.  Two rendering	contexts share an address space	if both	are
  nondirect using the same server, or if both are direct and owned by a
  single process.  Note	that in	the nondirect case it is not necessary for
  the calling threads to share an address space, only for their	related
  rendering contexts to	share an address space.

  Not all values for OpenGL state can be copied.  For example, pixel pack and
  unpack state,	render mode state, and select and feedback state are not
  copied.  The state that can be copied	is exactly the state that is
  manipulated by OpenGL	command	 glPushAttrib.

  An implicit glFlush is done by glXCopyContext	if src is the current context
  for the calling thread.

  If src is not	the current context for	the thread issuing the request,	then
  the state of the src context is undefined.

Notes

  Two rendering	contexts share an address space	if both	are nondirect using
  the same server, or if both are direct and owned by a	single process.

  A process is a single	execution environment, implemented in a	single
  address space, consisting of one or more threads.

  A thread is one of a set of subprocesses that	share a	single address space,
  but maintain separate	program	counters, stack	spaces,	and other related
  global data.	A thread that is the only member of its	subprocess group is
  equivalent to	a process.

Errors

  BadMatch is generated	if rendering contexts src and dst do not share an
  address space	or were	not created with respect to the	same screen.

  BadAccess is generated if dst	is current to any thread (including the
  calling thread) at the time glXCopyContext is	called.

  GLXBadCurrentWindow is generated if src is the current context and the
  current drawable is a	window that is no longer valid.

  GLXBadContext	is generated if	either src or dst is not a valid GLX context.

  BadValue is generated	if undefined mask bits are specified.

See Also

  glPushAttrib,	glXCreateContext, glXIsDirect

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glXCreateContext.3gl

Name

  glXCreateContext - create a new GLX rendering	context

C Specification

  GLXContext glXCreateContext( Display *dpy,
                   XVisualInfo *vis,
                   GLXContext shareList,
                   Bool direct )

Parameters

  dpy	     Specifies the connection to the X server.

  vis	     Specifies the visual that defines the frame buffer	resources
         available to the rendering	context.  It is	a pointer to an
         XVisualInfo structure, not	a visual ID or a pointer to a Visual.

  shareList  Specifies the context with	which to share display lists.  NULL
         indicates that no sharing is to take place.

  direct     Specifies whether rendering is to be done with a direct
         connection	to the graphics	system if possible (True) or through
         the X server (False).

Description

  glXCreateContext creates a GLX rendering context and returns its handle.
  This context can be used to render into both windows and GLX pixmaps.	 If
  glXCreateContext fails to create a rendering context,	NULL is	returned.

  If direct is True, then a direct rendering context is	created	if the
  implementation supports direct rendering and the connection is to an X
  server that is local.	 If direct is False, then a rendering context that
  renders through the X	server is always created.  Direct rendering provides
  a performance	advantage in some implementations.  However, direct rendering
  contexts cannot be shared outside a single process, and they cannot be used
  to render to GLX pixmaps.

  If shareList is not NULL, then all display-list indexes and definitions are
  shared by context shareList and by the newly created context.	 An arbitrary
  number of contexts can share a single	display-list space.  However, all
  rendering contexts that share	a single display-list space must themselves
  exist	in the same address space.  Two	rendering contexts share an address
  space	if both	are nondirect using the	same server, or	if both	are direct
  and owned by a single	process.  Note that in the nondirect case, it is not
  necessary for	the calling threads to share an	address	space, only for	their
  related rendering contexts to	share an address space.

Notes

  XVisualInfo is defined in Xutil.h.  It is a structure	that includes visual,
  visualID, screen, and	depth elements.

  A process is a single	execution environment, implemented in a	single
  address space, consisting of one or more threads.

  A thread is one of a set of subprocesses that	share a	single address space,
  but maintain separate	program	counters, stack	spaces,	and other related
  global data.	A thread that is the only member of its	subprocess group is
  equivalent to	a process.

Errors

  NULL is returned if execution	fails on the client side.

  BadMatch is generated	if the context to be created would not share the
  address space	or the screen of the context specified by shareList.

  BadValue is generated	if vis is not a	valid visual (e.g., if the GLX
  implementation does not support it).

  GLXBadContext	is generated if	shareList is not a GLX context and is not
  NULL.

  BadAlloc is generated	if the server does not have enough resources to
  allocate the new context.

See Also

  glXDestroyContext, glXGetConfig, glXIsDirect,	glXMakeCurrent

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glXCreateGLXPixmap.3gl

Name

  glXCreateGLXPixmap - create an off-screen GLX	rendering area

C Specification

  GLXPixmap glXCreateGLXPixmap(	Display	*dpy,
                XVisualInfo *vis,
                Pixmap pixmap )

Parameters

  dpy	  Specifies the	connection to the X server.

  vis	  Specifies the	visual that defines the	structure of the rendering
      area.	 It is a pointer to an XVisualInfo structure, not a visual ID
      or a pointer to a Visual.

  pixmap  Specifies the	X pixmap that will be used as the front	left color
      buffer of the	off-screen rendering area.

Description

  glXCreateGLXPixmap creates an	off-screen rendering area and returns its
  XID.	Any GLX	rendering context that was created with	respect	to vis can be
  used to render into this off-screen area.  Use glXMakeCurrent	to associate
  the rendering	area with a GLX	rendering context.

  The X	pixmap identified by pixmap is used as the front left buffer of	the
  resulting off-screen rendering area.	All other buffers specified by vis,
  including color buffers other	than the front left buffer, are	created
  without externally visible names.  GLX pixmaps with double-buffering are
  supported.  However, glXSwapBuffers is ignored by these pixmaps.

  Direct rendering contexts cannot be used to render into GLX pixmaps.

Notes

  XVisualInfo is defined in Xutil.h.  It is a structure	that includes visual,
  visualID, screen, and	depth elements.

Errors

  BadMatch is generated	if the depth of	pixmap does not	match the
  GLX_BUFFER_SIZE value	of vis,	or if pixmap was not created with respect to
  the same screen as vis.

  BadValue is generated	if vis is not a	valid XVisualInfo pointer (e.g., if
  the GLX implementation does not support this visual).

  BadPixmap is generated if pixmap is not a valid pixmap.

  BadAlloc is generated	if the server cannot allocate the GLX pixmap.

See Also

  glXCreateContext, glXIsDirect, glXMakeCurrent

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glXDestroyContext.3gl

Name

  glXDestroyContext - destroy a	GLX context

C Specification

  void glXDestroyContext( Display *dpy,
              GLXContext ctx )

Parameters

  dpy  Specifies the connection	to the X server.

  ctx  Specifies the GLX context to be destroyed.

Description

  If GLX rendering context ctx is not current to any thread,
  glXDestroyContext  destroys it immediately.  Otherwise, ctx is destroyed
  when it becomes not current to any thread.  In either	case, the resource ID
  referenced by	ctx is freed immediately.

Errors

  GLXBadContext	is generated if	ctx is not a valid GLX context.

See Also

  glXCreateContext, glXMakeCurrent

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glXDestroyGLXPixmap.3gl

Name

  glXDestroyGLXPixmap -	destroy	a GLX pixmap

C Specification

  void glXDestroyGLXPixmap( Display *dpy,
                GLXPixmap pix )

Parameters

  dpy  Specifies the connection	to the X server.

  pix  Specifies the GLX pixmap	to be destroyed.

Description

  If GLX pixmap	pix is not current to any client, glXDestroyGLXPixmap
  destroys it immediately.  Otherwise, pix is destroyed	when it	becomes	not
  current to any client.  In either case, the resource ID is freed
  immediately.

Errors

  GLXBadPixmap is generated if pix is not a valid GLX pixmap.

See Also

  glXCreateGLXPixmap, glXMakeCurrent

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glXGetConfig.3gl

Name

  glXGetConfig - return	information about GLX visuals

C Specification

  int glXGetConfig( Display *dpy,
            XVisualInfo	*vis,
            int	attrib,
            int	*value )

Parameters

  dpy	  Specifies the	connection to the X server.

  vis	  Specifies the	visual to be queried.  It is a pointer to an
      XVisualInfo structure, not a visual ID or a pointer to a Visual.

  attrib  Specifies the	visual attribute to be returned.

  value	  Returns the requested	value.

Description

  glXGetConfig sets value to the attrib	value of windows or GLX	pixmaps
  created with respect to vis.	glXGetConfig returns an	error code if it
  fails	for any	reason.	 Otherwise, zero is returned.

  attrib is one	of the following:

  GLX_USE_GL		True if	OpenGL rendering is supported by this visual,
            False otherwise.

  GLX_BUFFER_SIZE	Number of bits per color buffer.  For RGBA visuals,
            GLX_BUFFER_SIZE	is the sum of GLX_RED_SIZE,
            GLX_GREEN_SIZE,	GLX_BLUE_SIZE, and GLX_ALPHA_SIZE.
            For color index	visuals, GLX_BUFFER_SIZE is the	size
            of the color indexes.

  GLX_LEVEL		Frame buffer level of the visual.  Level zero is the
            default	frame buffer.  Positive	levels correspond to
            frame buffers that overlay the default buffer, and
            negative levels	correspond to frame buffers that
            underlay the default buffer.

  GLX_RGBA		True if	color buffers store red, green,	blue, and
            alpha values, False if they store color	indexes.

  GLX_DOUBLEBUFFER	True if	color buffers exist in front/back pairs	that
            can be swapped,	False otherwise.

  GLX_STEREO		True if	color buffers exist in left/right pairs,
            False otherwise.

  GLX_AUX_BUFFERS	Number of auxiliary color buffers that are available.
            Zero indicates that no auxiliary color buffers exist.

  GLX_RED_SIZE		Number of bits of red stored in	each color buffer.
            Undefined if GLX_RGBA is False.

  GLX_GREEN_SIZE	Number of bits of green	stored in each color buffer.
            Undefined if GLX_RGBA is False.

  GLX_BLUE_SIZE		Number of bits of blue stored in each color buffer.
            Undefined if GLX_RGBA is False.

  GLX_ALPHA_SIZE	Number of bits of alpha	stored in each color buffer.
            Undefined if GLX_RGBA is False.

  GLX_DEPTH_SIZE	Number of bits in the depth buffer.

  GLX_STENCIL_SIZE	Number of bits in the stencil buffer.

  GLX_ACCUM_RED_SIZE	Number of bits of red stored in	the accumulation
            buffer.

  GLX_ACCUM_GREEN_SIZE	Number of bits of green	stored in the accumulation
            buffer.

  GLX_ACCUM_BLUE_SIZE	Number of bits of blue stored in the accumulation
            buffer.

  GLX_ACCUM_ALPHA_SIZE	Number of bits of alpha	stored in the accumulation
            buffer.

  The X	protocol allows	a single visual	ID to be instantiated with different
  numbers of bits per pixel.  Windows or GLX pixmaps that will be rendered
  with OpenGL, however,	must be	instantiated with a color buffer depth of
  GLX_BUFFER_SIZE.

  Although a GLX implementation	can export many	visuals	that support OpenGL
  rendering, it	must support at	least two.  One	is an RGBA visual with at
  least	one color buffer, a stencil buffer of at least 1 bit, a	depth buffer
  of at	least 12 bits, and an accumulation buffer.  Alpha bitplanes are
  optional in this visual.  However, its color buffer size must	be as great
  as that of the deepest TrueColor, DirectColor,  PseudoColor, or StaticColor
  visual supported on level zero, and it must itself be	made available on
  level	zero.

  The other required visual is a color index one with at least one color
  buffer, a stencil buffer of at least 1 bit, and a depth buffer of at least
  12 bits.  This visual	must have as many color	bitplanes as the deepest
  PseudoColor or StaticColor visual supported on level zero, and it must
  itself be made available on level zero.

  Applications are best	written	to select the visual that most closely meets
  their	requirements.  Creating	windows	or GLX pixmaps with unnecessary
  buffers can result in	reduced	rendering performance as well as poor
  resource allocation.

Notes

  XVisualInfo is defined in Xutil.h.  It is a structure	that includes visual,
  visualID, screen, and	depth elements.

Errors

  GLX_NO_EXTENSION is returned if dpy does not support the GLX extension.

  GLX_BAD_SCREEN is returned if	the screen of vis does not correspond to a
  screen.

  GLX_BAD_ATTRIB is returned if	attrib is not a	valid GLX attribute.

  GLX_BAD_VISUAL is returned if	vis doesn't support GLX	and an attribute
  other	than GLX_USE_GL	is requested.

See Also

  glXChooseVisual, glXCreateContext

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glXGetCurrentContext.3gl

Name

  glXGetCurrentContext - return	the current context

C Specification

  GLXContext glXGetCurrentContext( void	)

Description

  glXGetCurrentContext returns the current context, as specified by
  glXMakeCurrent.  If there is no current context, NULL	is returned.

  glXGetCurrentContext returns client-side information.	 It does not make a
  round	trip to	the server.

See Also

  glXCreateContext, glXMakeCurrent

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glXGetCurrentDrawable.3gl

Name

  glXGetCurrentDrawable	- return the current drawable

C Specification

  GLXDrawable glXGetCurrentDrawable( void )

Description

  glXGetCurrentDrawable	returns	the current drawable, as specified by
  glXMakeCurrent.  If there is no current drawable, None is returned.

  glXGetCurrentDrawable	returns	client-side information.  It does not make a
  round	trip to	the server.

See Also

  glXCreateGLXPixmap, glXMakeCurrent

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glXIntro.3gl

Name

  glXIntro - Introduction to OpenGL in the X window system

OVERVIEW
  OpenGL is a high-performance 3-D-oriented renderer.  It is available in the
  X window system through the GLX extension.  Use glXQueryExtension and
  glXQueryVersion to establish whether the GLX extension is supported by an X
  server, and if so, what version is supported.

  GLX extended servers make a subset of	their visuals available	for OpenGL
  rendering.  Drawables	created	with these visuals can also be rendered	using
  the core X renderer and with the renderer of any other X extension that is
  compatible with all core X visuals.

  GLX extends drawables	with several buffers other than	the standard color
  buffer.  These buffers include back and auxiliary color buffers, a depth
  buffer, a stencil buffer, and	a color	accumulation buffer.  Some or all are
  included in each X visual that supports OpenGL.

  To render using OpenGL into an X drawable, you must first choose a visual
  that defines the required OpenGL buffers.  glXChooseVisual can be used to
  simplify selecting a compatible visual.  If more control of the selection
  process is required, use XGetVisualInfo and glXGetConfig to select among
  all the available visuals.

  Use the selected visual to create both a GLX context and an X	drawable.
  GLX contexts are created with	glXCreateContext, and drawables	are created
  with either XCreateWindow or glXCreateGLXPixmap.  Finally, bind the context
  and the drawable together using glXMakeCurrent.  This	context/drawable pair
  becomes the current context and current drawable, and	it is used by all
  OpenGL commands until	glXMakeCurrent is called with different	arguments.

  Both core X and OpenGL commands can be used to operate on the	current
  drawable.  The X and OpenGL command streams are not synchronized, however,
  except at explicitly created boundaries generated by calling glXWaitGL,
  glXWaitX, XSync, and glFlush.

Examples

  Below	is the minimum code required to	create an RGBA-format, OpenGL-
  compatible X window and clear	it to yellow.  The code	is correct, but	it
  does not include any error checking.	Return values dpy, vi, cx, cmap, and
  win should all be tested.

  #include <GL/glx.h> #include <GL/gl.h> #include <unistd.h>

  static int attributeList[] = { GLX_RGBA, None	};

  static Bool WaitForNotify(Display *d,	XEvent *e, char	*arg) {
      return (e->type == MapNotify) && (e->xmap.window == (Window)arg);	}

  int main(int argc, char **argv) {
      Display *dpy;
      XVisualInfo *vi;
      Colormap cmap;
      XSetWindowAttributes swa;
      Window win;
      GLXContext cx;
      XEvent event;

      /* get a connection */
      dpy = XOpenDisplay(0);

      /* get an	appropriate visual */
      vi = glXChooseVisual(dpy,	DefaultScreen(dpy), attributeList);

      /* create	a GLX context */
      cx = glXCreateContext(dpy, vi, 0,	GL_TRUE);

      /* create	a color	map */
      cmap = XCreateColormap(dpy, RootWindow(dpy, vi->screen),
                 vi->visual, AllocNone);

      /* create	a window */
      swa.colormap = cmap;
      swa.border_pixel = 0;
      swa.event_mask = StructureNotifyMask;
      win = XCreateWindow(dpy, RootWindow(dpy, vi->screen), 0, 0, 100, 100,
              0, vi->depth,	InputOutput, vi->visual,
              CWBorderPixel|CWColormap|CWEventMask,	&swa);
      XMapWindow(dpy, win);
      XIfEvent(dpy, &event, WaitForNotify, (char*)win);

      /* connect the context to	the window */
      glXMakeCurrent(dpy, win, cx);

      /* clear the buffer */
      glClearColor(1,1,0,1);
      glClear(GL_COLOR_BUFFER_BIT);
      glFlush();

      /* wait a	while */
      sleep(10); }

Notes

  A color map must be created and passed to XCreateWindow.  See	the example
  code above.

  A GLX	context	must be	created	and attached to	an X drawable before OpenGL
  commands can be executed.  OpenGL commands issued while no context/drawable
  pair is current are ignored.

  Exposure events indicate that	all buffers associated with the	specified
  window may be	damaged	and should be repainted.  Although certain buffers of
  some visuals on some systems may never require repainting (the depth
  buffer, for example),	it is incorrect	to code	assuming that these buffers
  will not be damaged.

  GLX commands manipulate XVisualInfo structures rather	than pointers to
  visuals or visual IDs.  XVisualInfo structures contain visual, visualID,
  screen, and depth elements, as well as other X-specific information.

See Also

  glFinish, glFlush, glXChooseVisual, glXCopyContext, glXCreateContext,
  glXCreateGLXPixmap, glXDestroyContext, glXGetConfig, glXIsDirect,
  glXMakeCurrent, glXQueryExtension, glXQueryVersion, glXSwapBuffers,
  glXUseXFont, glXWaitGL, glXWaitX, XCreateColormap, XCreateWindow, XSync

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glXIsDirect.3gl

Name

  glXIsDirect -	indicate whether direct	rendering is enabled

C Specification

  Bool glXIsDirect( Display *dpy,
            GLXContext ctx )

Parameters

  dpy  Specifies the connection	to the X server.

  ctx  Specifies the GLX context that is being queried.

Description

  glXIsDirect returns True if ctx is a direct rendering	context, False
  otherwise.  Direct rendering contexts	pass rendering commands	directly from
  the calling process's	address	space to the rendering system, bypassing the
  X server.  Nondirect rendering contexts pass all rendering commands to the
  X server.

Errors

  GLXBadContext	is generated if	ctx is not a valid GLX context.

See Also

  glXCreateContext

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glXMakeCurrent.3gl

Name

  glXMakeCurrent - attach a GLX	context	to a window or a GLX pixmap

C Specification

  Bool glXMakeCurrent( Display *dpy,
               GLXDrawable drawable,
               GLXContext ctx )

Parameters

  dpy	    Specifies the connection to	the X server.

  drawable  Specifies a	GLX drawable.  Must be either an X window ID or	a GLX
        pixmap ID.

  ctx	    Specifies a	GLX rendering context that is to be attached to
        drawable.

Description

  glXMakeCurrent does two things: It makes ctx the current GLX rendering
  context of the calling thread, replacing the previously current context if
  there	was one, and it	attaches ctx to	a GLX drawable,	either a window	or a
  GLX pixmap.  As a result of these two	actions, subsequent OpenGL rendering
  calls	use rendering context ctx to modify GLX	drawable drawable.  Because
  glXMakeCurrent always	replaces the current rendering context with ctx,
  there	can be only one	current	context	per thread.

  Pending commands to the previous context, if any, are	flushed	before it is
  released.

  The first time ctx is	made current to	any thread, its	viewport is set	to
  the full size	of drawable.  Subsequent calls by any thread to
  glXMakeCurrent with ctx have no effect on its	viewport.

  To release the current context without assigning a new one, call
  glXMakeCurrent with drawable and ctx set to None and NULL respectively.

  glXMakeCurrent returns True if it is successful, False otherwise.  If	False
  is returned, the previously current rendering	context	and drawable (if any)
  remain unchanged.

Notes

  A process is a single-execution environment, implemented in a	single
  address space, consisting of one or more threads.

  A thread is one of a set of subprocesses that	share a	single address space,
  but maintain separate	program	counters, stack	spaces,	and other related
  global data.	A thread that is the only member of its	subprocess group is
  equivalent to	a process.

Errors

  BadMatch is generated	if drawable was	not created with the same X screen
  and visual as	ctx.  It is also generated if drawable is None and ctx is not
  None.

  BadAccess is generated if ctx	was current to another thread at the time
  glXMakeCurrent was called.

  GLXBadDrawable is generated if drawable is not a valid GLX drawable.

  GLXBadContext	is generated if	ctx is not a valid GLX context.

  GLXBadContextState is	generated if glXMakeCurrent is called between a
  glBegin and the corresponding	call to	glEnd.

  GLXBadContextState is	also generated if the rendering	context	current	to
  the calling thread has OpenGL	renderer state GL_FEEDBACK or GL_SELECT.

  GLXBadCurrentWindow is generated if there are	pending	OpenGL commands	for
  the previous context and the current drawable	is a window that is no longer
  valid.

  BadAlloc may be generated if the server has delayed allocation of ancillary
  buffers until	glXMakeCurrent is called, only to find that it has
  insufficient resources to complete the allocation.

See Also

  glXCreateContext, glXCreateGLXPixmap

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glXQueryExtension.3gl

Name

  glXQueryExtension - indicate whether the GLX extension is supported

C Specification

  Bool glXQueryExtension( Display *dpy,
              int *errorBase,
              int *eventBase )

Parameters

  dpy	     Specifies the connection to the X server.

  errorBase  Returns the base error code of the	GLX server extension.

  eventBase  Returns the base event code of the	GLX server extension.

Description

  glXQueryExtension returns True if the	X server of connection dpy supports
  the GLX extension, False otherwise.  If True is returned, then errorBase
  and eventBase	return the error base and event	base of	the GLX	extension.
  Otherwise, errorBase and eventBase are unchanged.

  errorBase and	eventBase do not return	values if they are specified as	NULL.

Notes

  eventBase is included	for future extensions.	GLX does not currently define
  any events.

See Also

  glXQueryVersion

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glXQueryVersion.3gl

Name

  glXQueryVersion - return the version numbers of the GLX extension

C Specification

  Bool glXQueryVersion(	Display	*dpy,
            int *major,
            int *minor )

Parameters

  dpy	 Specifies the connection to the X server.

  major	 Returns the major version number of the GLX server extension.

  minor	 Returns the minor version number of the GLX server extension.

Description

  glXQueryVersion returns the major and minor version numbers of the GLX
  extension available on the connection dpy. The client library and the
  server implementations must have the same major version number or else
  glXQueryVersion will return False. The minor version that is returned is
  the minimum of the two minor version numbers.

  major and minor do not return values if they are specified as NULL.

Errors

  glXQueryVersion returns False	if it fails, True otherwise.  major and	minor
  are not updated when False is	returned.

See Also

  glXQueryExtension

Introduction | Alphabetic | Specification

Last Edited: Thu Mar 16, 1995

AFV


glXSwapBuffers.3gl

Name

  glXSwapBuffers - make	back buffer visible

C Specification

  void glXSwapBuffers( Display *dpy,
               GLXDrawable drawable )

Parameters

  dpy	    Specifies the connection to	the X server.

  drawable  Specifies the window whose buffers are to be swapped.

Description

  glXSwapBuffers promotes the contents of the back buffer of drawable to
  become the contents of the front buffer of drawable.	The contents of	the
  back buffer then become undefined.  The update typically takes place during
  the vertical retrace of the monitor, rather than immediately after
  glXSwapBuffers is called.  All GLX rendering contexts	share the same notion
  of which are front buffers and which are back	buffers.

  An implicit glFlush is done by glXSwapBuffers	before it returns.
  Subsequent OpenGL commands can be issued immediately after calling
  glXSwapBuffers, but are not executed until the buffer	exchange is
  completed.

  If drawable was not created with respect to a	double-buffered	visual,
  glXSwapBuffers has no	effect,	and no error is	generated.

Notes

  Synchronization of multiple GLX contexts rendering to	the same double-
  buffered window is the responsibility	of the clients.	 The X
  Synchronization Extension can	be used	to facilitate such cooperation.

Errors

  GLXBadDrawable is generated if drawable is not a valid GLX drawable.

  GLXBadCurrentWindow is generated if dpy and drawable are respectively	the
  display and drawable associated with the current context of the calling
  thread, and drawable identifies a window that	is no longer valid.

See Also

  glFlush

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glXUseXFont.3gl

Name

  glXUseXFont -	create bitmap display lists from an X font

C Specification

  void glXUseXFont( Font font,
            int	first,
            int	count,
            int	listBase )

Parameters

  font	    Specifies the font from which character glyphs are to be taken.

  first	    Specifies the index	of the first glyph to be taken.

  count	    Specifies the number of glyphs to be taken.

  listBase  Specifies the index	of the first display list to be	generated.

Description

  glXUseXFont generates	count display lists, named listBase through
  listBase+count-1, each containing a single glBitmap command.	The
  parameters of	the glBitmap command of	display	list listBase+i	are derived
  from glyph first+i.  Bitmap parameters xorig,	yorig, width, and height are
  computed from	font metrics as	descent-1, -lbearing, rbearing-lbearing, and
  ascent+descent, respectively.	 xmove is taken	from the glyph's width
  metric, and ymove is set to zero.  Finally, the glyph's image	is converted
  to the appropriate format for	glBitmap.

  Using	glXUseXFont may	be more	efficient than accessing the X font and
  generating the display lists explicitly, both	because	the display lists are
  created on the server	without	requiring a round trip of the glyph data, and
  because the server may choose	to delay the creation of each bitmap until it
  is accessed.

  Empty	display	lists are created for all glyphs that are requested and	are
  not defined in font.	glXUseXFont is ignored if there	is no current GLX
  context.

Errors

  BadFont is generated if font is not a	valid font.

  GLXBadContextState is	generated if the current GLX context is	in display-
  list construction mode.

  GLXBadCurrentWindow is generated if the drawable associated with the
  current context of the calling thread	is a window, and that window is	no
  longer valid.

See Also

  glBitmap, glXMakeCurrent

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glXWaitGL.3gl

Name

  glXWaitGL - complete GL execution prior to subsequent	X calls

C Specification

  void glXWaitGL( void )

Description

  OpenGL rendering calls made prior to glXWaitGL are guaranteed	to be
  executed before X rendering calls made after glXWaitGL.  Although this same
  result can be	achieved using glFinish, glXWaitGL does	not require a round
  trip to the server, and it is	therefore more efficient in cases where
  client and server are	on separate machines.

  glXWaitGL is ignored if there	is no current GLX context.

Notes

  glXWaitGL may	or may not flush the X stream.

Errors

  GLXBadCurrentWindow is generated if the drawable associated with the
  current context of the calling thread	is a window, and that window is	no
  longer valid.

See Also

  glFinish, glFlush, glXWaitX, XSync

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glXWaitX.3gl

Name

  glXWaitX - complete X	execution prior	to subsequent OpenGL calls

C Specification

  void glXWaitX( void )

Description

  X rendering calls made prior to glXWaitX are guaranteed to be	executed
  before OpenGL	rendering calls	made after glXWaitX.  Although this same
  result can be	achieved using XSync, glXWaitX does not	require	a round	trip
  to the server, and it	is therefore more efficient in cases where client and
  server are on	separate machines.

  glXWaitX is ignored if there is no current GLX context.

Notes

  glXWaitX may or may not flush	the OpenGL stream.

Errors

  GLXBadCurrentWindow is generated if the drawable associated with the
  current context of the calling thread	is a window, and that window is	no
  longer valid.

See Also

  glFinish, glFlush, glXWaitGL,	XSync

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


Accueil

Accueil CHM

Normal, dit comme ça,
vous ne risquez pas de comprendre.